• spring cloud服务发现组件Eureka详解


     

    Eureka是Netflix开发的服务发现组件,本身是一个基于REST的服务。Spring Cloud将它集成在其子项目spring-cloud-netflix中,以实现Spring Cloud的服务发现功能。目前Eureka 项目相当活跃,代码更新相当频繁,目前最新的版本是1.5.5。Eureka 2.0也在紧锣密鼓地开发中,2.0将会带来更强的功能和更好的扩展性,但是由于还没有Release,故而不作讨论。

    本文讲解的Spring Cloud Camden SR1所使用的Eureka版本是1.4.11,还是比较新的。同时有了Eureka 1.x的基础,未来上手Eureka 2.x也会比较容易。

    Eureka的Github:https://github.com/Netflix/Eureka

    Region、Zone解析

    Eureka的官方文档对regin、zone几乎没有提及,由于概念抽象,新手很难理解。因此,在分析Eureka原理之前,我们先来了解一下region、zone、Eureka集群三者的关系,如图4-2。

    regin-zone-eureka

    图4-2 region、zone、Eureka集群之间的关系

    region和zone(或者Availability Zone)均是AWS的概念。在非AWS环境下,我们可以简单地将region理解为Eureka集群,zone理解成机房。这样图4-2就很好理解了——一个Eureka集群被部署在了zone1机房和zone2机房中。

    对region和zone感兴趣的读者可前往http://blog.csdn.net/awschina/article/details/17639191 扩展阅读。Spring Cloud中默认的region是us-east-1 。

    Eureka架构

    eureka 架构

    图4-3 Eureka架构图

    图4-3是来自Eureka官方的架构图,大致描述了Eureka集群的工作过程。图中包含的组件非常多,可能比较难以理解,我们用通俗易懂的语言解释一下:

    • Application Service 相当于本书中的服务提供者,Application Client相当于本书中的服务消费者;
    • Make Remote Call,可以简单理解为调用RESTful API;
    • us-east-1c、us-east-1d等都是zone,它们都属于us-east-1这个region;

    由图可知,Eureka包含两个组件:Eureka Server 和 Eureka Client,它们的作用如下:

    • Eureka Client是一个Java客户端,用于简化与Eureka Server的交互;
    • Eureka Server提供服务发现的能力,各个微服务启动时,会通过Eureka Client向Eureka Server进行注册自己的信息(例如网络信息),Eureka Server会存储该服务的信息;
    • 微服务启动后,会周期性地向Eureka Server发送心跳(默认周期为30秒)以续约自己的信息。如果Eureka Server在一定时间内没有接收到某个微服务节点的心跳,Eureka Server将会注销该微服务节点(默认90秒);
    • 每个Eureka Server同时也是Eureka Client,多个Eureka Server之间通过复制的方式完成服务注册表的同步;
    • Eureka Client会缓存Eureka Server中的信息。即使所有的Eureka Server节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者。

    Why Eureka?

    那么为什么我们在项目中使用了Eureka呢?我大致总结了一下,有以下几方面的原因:

    1)它提供了完整的Service RegistryService Discovery实现

    首先是提供了完整的实现,并且也经受住了Netflix自己的生产环境考验,相对使用起来会比较省心。

    2)和Spring Cloud无缝集成

    我们的项目本身就使用了Spring Cloud和Spring Boot,同时Spring Cloud还有一套非常完善的开源代码来整合Eureka,所以使用起来非常方便。

    另外,Eureka还支持在我们应用自身的容器中启动,也就是说我们的应用启动完之后,既充当了Eureka的角色,同时也是服务的提供者。这样就极大的提高了服务的可用性。

    这一点是我们选择Eureka而不是zketcd等的主要原因,为了提高配置中心的可用性和降低部署复杂度,我们需要尽可能地减少外部依赖。

    3)Open Source

    最后一点是开源,由于代码是开源的,所以非常便于我们了解它的实现原理和排查问题。

    3、Dive into Eureka

    相信大家看到这里,已经对Eureka有了一个初步的认识,接下来我们就来深入了解下它吧~

    3.1 Overview

    3.1.1 Basic Architecture

    图1

    上图简要描述了Eureka的基本架构,由3个角色组成:

    Eureka Server

    提供服务注册和发现

    Service Provider

    服务提供方

    将自身服务注册到Eureka,从而使服务消费方能够找到

    Service Consumer

    服务消费方

    从Eureka获取注册服务列表,从而能够消费服务

    需要注意的是,上图中的3个角色都是逻辑角色。在实际运行中,这几个角色甚至可以是同一个实例,比如在我们项目中,Eureka Server和Service Provider就是同一个JVM进程。

    3.1.2 More in depth

    图2

    上图更进一步的展示了3个角色之间的交互。

    1. Service Provider会向Eureka Server做Register(服务注册)、Renew(服务续约)、Cancel(服务下线)等操作。
    2. Eureka Server之间会做注册服务的同步,从而保证状态一致
    3. Service Consumer会向Eureka Server获取注册服务列表,并消费服务

    3.2 Demo

    为了给大家一个更直观的印象,我们可以通过一个简单的demo来实际运行一下,从而对Eureka有更好的了解。

    3.2.1 Git Repository

    Git仓库:nobodyiam/spring-cloud-in-action

    这个项目使用了Spring Cloud相关类库,包括:

    3.2.2 准备工作

    Demo项目使用了Spring Cloud Config做配置,所以第一步先在本地启动Config Server。

    由于项目基于Spring Boot开发,所以直接运行com.nobodyiam.spring.cloud.in.action.config.ConfigServerApplication即可。

    3.2.3 Eureka Server Demo

    Eureka Server的Demo模块名是:eureka-server。

    3.2.3.1 Maven依赖

    eureka-server是一个基于Spring Boot的Web应用,我们首先需要做的就是在pom中引入Spring Cloud Eureka Server的依赖。

    <dependency>    
    <groupId>org.springframework.cloud</groupId>    
    <artifactId>spring-cloud-starter-eureka-server</artifactId><version>1.2.0.RELEASE</version>
    </dependency>
    

    3.2.3.2 启用Eureka Server

    启用Eureka Server非常简单,只需要加上@EnableEurekaServer即可。

    @EnableEurekaServer
    @SpringBootApplication
    public class EurekaServiceApplication {  
    public static void main(String[] args{   
    SpringApplication.run(EurekaServiceApplication.class, args);
    }
    }
    

    做完以上配置后,启动应用,Eureka Server就开始工作了!

    启动完,打开http://localhost:8761,就能看到启动成功的画面了。

    图3

    3.2.4 Service Provider and Service Consumer Demo

    Service Provider的Demo模块名是:reservation-service。

    Service Consumer的Demo模块名是:reservation-client。

    3.2.4.1 Maven依赖

    reservation-service和reservation-client都是基于Spring Boot的Web应用,我们首先需要做的就是在pom中引入Spring Cloud Eureka的依赖。

    <dependency>    
    <groupId>org.springframework.cloud</groupId>    
    <artifactId>spring-cloud-starter-eureka</artifactId>    <version>1.2.0.RELEASE</version>
    </dependency>
    

    3.2.4.2 启动Service Provider

    启用Service Provider非常简单,只需要加上@EnableDiscoveryClient即可。

    @EnableDiscoveryClient
    @SpringBootApplication
    public class ReservationServiceApplication { 
    public static void main(String[] args) {    newSpringApplicationBuilder(ReservationServiceApplication.class)
    .run(args);
    }
    }
    

    做完以上配置后,启动应用,Server Provider就开始工作了!

    启动完,打开http://localhost:8761,就能看到服务已经注册到Eureka Server了。

    图4

    3.2.4.3 启动Service Consumer

    启动Service Consumer其实和Service Provider一样,因为本质上Eureka提供的客户端是不区分Provider和Consumer的,一般情况下,Provider同时也会是Consumer。

    @EnableDiscoveryClient
    @SpringBootApplication
    public class ReservationClientApplication {  
    @Bean  
    CommandLineRunner runner(DiscoveryClient dc) {    
    return args -> {      
    dc.getInstances("reservation-service")              
    .forEach(si -> System.out.println(String.format(                      
    "Found %s %s:%s", si.getServiceId(), si.getHost(), si.getPort())));    
    };  
    }  
    public static void main(String[] args) {    SpringApplication.run(ReservationClientApplication.class, args);  
    }
    }
    

    上述代码中通过dc.getInstances(“reservation-service”)就能获取到当前所有注册的reservation-service服务。

    3.3 Eureka Server实现细节

    看了前面的demo,我们已经初步领略到了Spring Cloud和Eureka的强大之处,通过短短几行配置就实现了服务注册和发现!

    相信大家一定想了解Eureka是如何实现的吧,所以接下来我们继续Dive!首先来看下Eureka Server的几个对外接口实现。

    3.3.1 Register

    首先来看Register(服务注册),这个接口会在Service Provider启动时被调用来实现服务注册。同时,当Service Provider的服务状态发生变化时(如自身检测认为Down的时候),也会调用来更新服务状态。

    接口实现比较简单,如下图所示。

    1. ApplicationResource类接收Http服务请求,调用PeerAwareInstanceRegistryImpl的register方法
    2. PeerAwareInstanceRegistryImpl完成服务注册后,调用replicateToPeers向其它Eureka Server节点(Peer)做状态同步(异步操作)

    图5

    注册的服务列表保存在一个嵌套的hash map中:

    • 第一层hash map的key是app name,也就是应用名字
    • 第二层hash map的key是instance name,也就是实例名字

    3.2.4.2中的截图为例,RESERVATION-SERVICE就是app name,jason-mbp.lan:reservation-service:8000就是instance name。

    Hash map定义如下:

    private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry =new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();

    3.3.2 Renew

    Renew(服务续约)操作由Service Provider定期调用,类似于heartbeat。主要是用来告诉Eureka Server Service Provider还活着,避免服务被剔除掉。接口实现如下图所示。

    可以看到,接口实现方式和register基本一致:首先更新自身状态,再同步到其它Peer。

    图6

    3.3.3 Cancel

    Cancel(服务下线)一般在Service Provider shut down的时候调用,用来把自身的服务从Eureka Server中删除,以防客户端调用不存在的服务。接口实现如下图所示。

    图7

    3.3.4 Fetch Registries

    Fetch Registries由Service Consumer调用,用来获取Eureka Server上注册的服务。

    为了提高性能,服务列表在Eureka Server会缓存一份,同时每30秒更新一次。

    图8

    3.3.5 Eviction

    Eviction(失效服务剔除)用来定期(默认为每60秒)在Eureka Server检测失效的服务,检测标准就是超过一定时间没有Renew的服务。

    默认失效时间为90秒,也就是如果有服务超过90秒没有向Eureka Server发起Renew请求的话,就会被当做失效服务剔除掉。

    失效时间可以通过eureka.instance.leaseExpirationDurationInSeconds进行配置,定期扫描时间可以通过eureka.server.evictionIntervalTimerInMs进行配置。

    接口实现逻辑见下图:

    图9

    3.3.6 How Peer Replicates

    在前面的Register、Renew、Cancel接口实现中,我们看到了都会有replicateToPeers操作,这个就是用来做Peer之间的状态同步。

    通过这种方式,Service Provider只需要通知到任意一个Eureka Server后就能保证状态会在所有的Eureka Server中得到更新。

    具体实现方式其实很简单,就是接收到Service Provider请求的Eureka Server,把请求再次转发到其它的Eureka Server,调用同样的接口,传入同样的参数,除了会在header中标记isReplication=true,从而避免重复的replicate。

    Peer之间的状态是采用异步的方式同步的,所以不保证节点间的状态一定是一致的,不过基本能保证最终状态是一致的。

    结合服务发现的场景,实际上也并不需要节点间的状态强一致。在一段时间内(比如30秒),节点A比节点B多一个服务实例或少一个服务实例,在业务上也是完全可以接受的(Service Consumer侧一般也会实现错误重试和负载均衡机制)。

    所以按照CAP理论,Eureka的选择就是放弃C,选择AP。

    3.3.7 How Peer Nodes are Discovered

    那大家可能会有疑问,Eureka Server是怎么知道有多少Peer的呢?

    Eureka Server在启动后会调用EurekaClientConfig.getEurekaServerServiceUrls来获取所有的Peer节点,并且会定期更新。定期更新频率可以通过eureka.server.peerEurekaNodesUpdateIntervalMs配置。

    这个方法的默认实现是从配置文件读取,所以如果Eureka Server节点相对固定的话,可以通过在配置文件中配置来实现。

    如果希望能更灵活的控制Eureka Server节点,比如动态扩容/缩容,那么可以override getEurekaServerServiceUrls方法,提供自己的实现,比如我们的项目中会通过数据库读取Eureka Server列表。

    具体实现如下图所示:

    图10

    3.3.8 How New Peer Initializes

    最后再来看一下一个新的Eureka Server节点加进来,或者Eureka Server重启后,如何来做初始化,从而能够正常提供服务。

    具体实现如下图所示,简而言之就是启动时把自己当做是Service Consumer从其它Peer Eureka获取所有服务的注册信息。然后对每个服务,在自己这里执行Register,isReplication=true,从而完成初始化。

    图11

    3.4 Service Provider实现细节

    现在来看下Service Provider的实现细节,主要就是Register、Renew、Cancel这3个操作。

    3.4.1 Register

    Service Provider要对外提供服务,一个很重要的步骤就是把自己注册到Eureka Server上。

    这部分的实现比较简单,只需要在启动时和实例状态变化时调用Eureka Server的接口注册即可。需要注意的是,需要确保配置eureka.client.registerWithEureka=true。

    图12

    3.4.2 Renew

    Renew操作会在Service Provider端定期发起,用来通知Eureka Server自己还活着。 这里有两个比较重要的配置需要注意一下:

    1. instance.leaseRenewalIntervalInSeconds

    Renew频率。默认是30秒,也就是每30秒会向Eureka Server发起Renew操作。

    1. instance.leaseExpirationDurationInSeconds

    服务失效时间。默认是90秒,也就是如果Eureka Server在90秒内没有接收到来自Service Provider的Renew操作,就会把Service Provider剔除。

    具体实现如下:

    图13

    3.4.3 Cancel

    在Service Provider服务shut down的时候,需要及时通知Eureka Server把自己剔除,从而避免客户端调用已经下线的服务。

    逻辑本身比较简单,通过对方法标记@PreDestroy,从而在服务shut down的时候会被触发。

    图14

    3.4.4 How Eureka Servers are Discovered

    这里大家疑问又来了,Service Provider是怎么知道Eureka Server的地址呢?

    其实这部分的主体逻辑和3.3.7 How Peer Nodes are Discovered几乎是一样的。

    也是默认从配置文件读取,如果需要更灵活的控制,可以通过override getEurekaServerServiceUrls方法来提供自己的实现。定期更新频率可以通过eureka.client.eurekaServiceUrlPollIntervalSeconds配置。

    图15

    3.5 Service Consumer实现细节

    Service Consumer这块的实现相对就简单一些,因为它只涉及到从Eureka Server获取服务列表和更新服务列表。

    3.5.1 Fetch Service Registries

    Service Consumer在启动时会从Eureka Server获取所有服务列表,并在本地缓存。需要注意的是,需要确保配置eureka.client.shouldFetchRegistry=true。

    图16

    3.5.2 Update Service Registries

    由于在本地有一份缓存,所以需要定期更新,定期更新频率可以通过eureka.client.registryFetchIntervalSeconds配置。

    图17

    3.5.3 How Eureka Servers are Discovered

    Service Consumer和Service Provider一样,也有一个如何知道Eureka Server地址的问题。

    其实由于Service Consumer和Service Provider本质上是同一个Eureka客户端,所以这部分逻辑是一样的,这里就不再赘述了。详细信息见3.4.4节

    4. Summary

    本文主要介绍了Eureka的实现思路,通过深入了解Eureka Server、Service Provider、Service Consumer的实现,我们清晰地看到了服务注册、发现的一系列过程和实现方式。

    如何使用Spring Cloud Eureka?


    下面实际操作一下,创建两个微服务。先创建一个服务提供者,再创建一个服务消费者,服务消费者先通过普通的方式调用服务提供者,再通过Spring Eureka 调用服务提供者。

    先创建一个microservice-provider项目,作为服务提供者。 
    pom文件中引入Spring Boot的依赖:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.3.RELEASE</version>
    </parent>
    
    再添加Spring MVC的支持:
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>



    spring-boot-starter-parent定义了Spring Boot版本的基础依赖以及一些默认配置内容,比如,配置文件application.properties的位置等。 
    spring-boot-starter-web,是全栈web开发模块,包含嵌入式Tomcat,Spring MVC。 
    配置完pom依赖之后,再添加MicroServiceProviderApplication文件:

    @SpringBootApplication
    public class MicroServiceProviderApplication {
        public static void main(String[] args) {
            SpringApplication.run(MicroServiceProviderApplication.class, args);
        }
    }
    
    然后再添加一个Controller,用来提供服务,这里为了简单起见,直接返回hello world。
    
    @RestController
    public class ProviderController {
    
        @RequestMapping(value = "/provider", method = RequestMethod.GET)
        public String provider() {
            return "hello world";
        }
    }



    之后,编写配置文件application.properties,添加如下配置:

    server.port=8000
    spring.application.name=microservice-provider

    到这里,服务提供者项目就写完了,接下来,再写服务消费者微服务。 
    创建一个microservice-consumer项目,作为服务消费者。配置和服务提供者类似,pom文件同样添加spring-boot-starter-parent和spring-boot-starter-web的依赖。编写MicroServiceConsumeApplication作为启动类,并在启动类中实例化RestTemplate对象。

    @SpringBootApplication
    public class MicroServiceConsumeApplication {
        @Bean
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    
        public static void main(String[] args) {
            SpringApplication.run(MicroServiceConsumeApplication.class, args);
        }
    }
    
    然后创建ConsumerController作为消费者方法,用来去调用服务提供者,这里通过restTemplate去请求服务提供者的API:
    
    import org.springframework.web.client.RestTemplate;
    @RestController
    public class ConsumerController {
    
        @Autowired
        private RestTemplate restTemplate;
    
        @RequestMapping(value = "/consumer", method = RequestMethod.GET)
        public String consumer() {
           return this.restTemplate.getForEntity("http://localhost:8000/provider", String.class).getBody();
        }
    }
    


    接下来配置服务消费者的application.properties,添加如下内容:

    server.port=9000
    spring.application.name=microservice-consumer

    这样,服务消费者也写好了,本地启动这两个服务的话,访问http://localhost:9000/consumer,就可以看到返回的hello world。 
    但是这种调用方式,只是通过硬编码的方式,在程序中写死了服务提供者的地址,然后去请求的API,这样就会存在很多问题,试用场景有限,无法动态伸缩。 
    所以,就需要一个服务发现机制,服务消费者通过这种机制去发现服务提供者,然后再去调用,这样就不是硬编码了。 
    接下来,创建一个Eureka Server作为注册中心,然后再把服务提供者和服务消费者两个微服务进行改造,改造成Eureka Client,并且注册到注册中心。这样,当服务消费者再调用服务提供者的时候,就可以通过注册中心,获得服务提供者的地址,然后进行调用。

    接下来,创建Eureka Server项目:eurekaserver 
    在pom文件中给项目添加依赖:

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>
    </dependencies>



    之后再编写启动类:EurekaServerApplication

    @SpringBootApplication
    @EnableEurekaServer//声明这是一个Eureka server
    public class EurekaServerApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(EurekaServerApplication.class, args);
        }
    }
    


    在启动类上添加@EnableEurekaServer注解,声明这是一个Eureka Server。 
    最后在application.properties文件中,添加配置项:

    server.port=8761
    eureka.client.register-with-eureka=false
    eureka.client.fetch-registry=false
    eureka.client.service-url.defaultZone=http://localhost:8761/eureka/

    server.port=8761指明了项目启动的端口。 
    eureka.client.register-with-eureka=false,这个配置项表示是否将自己注册到Eureka Server,默认为true(默认情况下,Eureka Server也是一个Eureka Client),由于我们这里只有这一个Eureka Server,所以把这个配置项设置为false。 
    eureka.client.fetch-registry=false,这个配置项表示是否从Eureka Server获取配置信息,默认为true。由于我们这里只有这一个Eureka Server,所以把这个配置项设置为false。 
    eureka.client.service-url.defaultZone=http://localhost:8761/eureka/ 这个配置项是与Eureka Server交互的地址。注册服务、查询服务,都是这个地址。 
    经过上面几步配置,我们的Eureka Server就开发完成了,接下来启动这个项目,在浏览器输入http://localhost:8761,就可以看到Eureka Server 的界面。 


    接下来,改造服务提供者和服务消费者。 
    先修改服务提供者,在pom文件中添加依赖:

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
    </dependency>

    然后修改MicroServiceProviderApplication,在上面添加注解@EnableDiscoveryClient,声明是一个Eureka client:

    @EnableDiscoveryClient//声明是一个Eureka client
    @SpringBootApplication
    public class MicroServiceProviderApplication {
        public static void main(String[] args) {
            SpringApplication.run(MicroServiceProviderApplication.class, args);
        }
    }
    


    之后配置文件中添加配置项:

    eureka.client.service-url.defaultZone=http://localhost:8761/eureka/

    配置完成之后启动项目,项目启动的时候,就会作为一个Eureka client向Eureka Server注册信息,注册的地址为http://localhost:8761/eureka/,这个地址就是我们Eureka Server启动的时候,指定的注册地址。 
    此时再刷新http://localhost:8761,就可以看到Eureka Server 的界面上已经注册了一个服务了,服务名称是microservice-provider。 


    然后继续再改造服务消费者,改造方式和microservice-provider一样,也是在pom文件中添加spring-cloud-starter-eureka的依赖,在MicroServiceConsumeApplication上面添加注解@EnableDiscoveryClient注解,在application.properties中添加服务注册地址eureka.client.service-url.defaultZone=http://localhost:8761/eureka/ 
    除此之外,在TestTemplate生成的地方,添加@LoadBalanced注解,使用负载均衡。(关于负载均衡,后续文章接续介绍)

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    


    修改restTemplate调用的地址,原来是调用http://localhost:8000/provider,现在改成调用http://MICROSERVICE-PROVIDER/provider。 
    之后,再启动microservice-consumer。刷新http://localhost:8761,可以看到,microservice-consumer也注册到Eureka Server上了。 


    浏览器输入http://localhost:9000/consumer,调用成功。

    上面讲了通过Eureka,搭建微服务并且进行服务治理的过程。先搭建了一个Eureka Server,然后再创建两个Eureka Client,一个作为服务消费者,另一个作为服务提供者。 
    服务提供者先去Eureka Server上面注册自己的服务 
    服务消费者去Eureka Server上获取服务,然后进行消费。

    参考资料: 
    1.《Spring Cloud与Docker微服务架构实战》 周立 著 
    2.《Spring Cloud微服务实战》 翟永超 著
     

    正因为当初对未来做了太多的憧憬,所以对现在的自己尤其失望。生命中曾经有过的所有灿烂,终究都需要用寂寞来偿还。
  • 相关阅读:
    Azure DevOps 介绍
    vs如何将attach to process放入到toolbar中,以方便调试
    AtCoder Regular Contest 133
    Codeforces Round #778 (Div. 1 + Div. 2, based on Technocup 2022 Final Round) AE 题解
    LGP6326 Shopping 点分治+dp
    CF527E Data Center Drama
    Silverlight Expression Blend之ImageButton
    Expression Blend实战开发技巧——ImageButton详解
    uniapp安卓NFC读取
    uniapp拖动/缩放nvue页面
  • 原文地址:https://www.cnblogs.com/candlia/p/11920023.html
Copyright © 2020-2023  润新知