1、eureka示范例子

    Github地址 https://github.com/liushangzaibeijing/eureka

2、 Eureka的一些概念

Register:服务注册

当Eureka客户端向Eureka Server注册时,它提供自身的元数据,比如IP地址、端口,运行状况指示符URL,主页等。

Renew:服务续约

Eureka客户会每隔30秒发送一次心跳来续约。 通过续约来告知Eureka Server该Eureka客户仍然存在,没有出现问题。 正常情况下,如果Eureka Server在90秒没有收到Eureka客户的续约,它会将实例从其注册表中删除。 建议不要更改续约间隔。

Fetch Registries:获取注册列表信息

Eureka客户端从服务器获取注册表信息,并将其缓存在本地。客户端会使用该信息查找其他服务,从而进行远程调用。该注册列表信息定期(每30秒钟)更新一次。每次返回注册列表信息可能与Eureka客户端的缓存信息不同, Eureka客户端自动处理。如果由于某种原因导致注册列表信息不能及时匹配,Eureka客户端则会重新获取整个注册表信息。 Eureka服务器缓存注册列表信息,整个注册表以及每个应用程序的信息进行了压缩,压缩内容和没有压缩的内容完全相同。Eureka客户端和Eureka 服务器可以使用JSON / XML格式进行通讯。在默认的情况下Eureka客户端使用压缩JSON格式来获取注册列表的信息。

Cancel:服务下线

Eureka客户端在程序关闭时向Eureka服务器发送取消请求。 发送请求后,该客户端实例信息将从服务器的实例注册表中删除。该下线请求不会自动完成,它需要调用以下内容:

    DiscoveryManager.getInstance().shutdownComponent();

Eviction 服务剔除

在默认的情况下,当Eureka客户端连续90秒没有向Eureka服务器发送服务续约,即心跳,Eureka服务器会将该服务实例从服务注册列表删除,即服务剔除。

3、源码分析

从例子中我们可以看到对应的eureka 客户端添加相关的eureka依赖后只需要在主应用的启动类上添加@EnableEurekaClient 或者 @EnableDiscoveryClient即可

@EnableDiscoveryClient和@EnableEurekaClient共同点就是:都是能够让注册中心能够发现,扫描到该服务。

不同点:@EnableEurekaClient只适用于Eureka作为注册中心,@EnableDiscoveryClient 可以是其他注册中心。

使用该注解会在服务启动的过程中初始化DiscoverClient类。同时也是我们分析Eureka组件的入口类,下面让我们来具体分析一个一个微服务注册的过程。

DiscoveryClient类。相关的UML类图如下:

     在spring服务启动过程中DiscoverClient类的initScheduledTasks()方法会被调用

该方法主要做了

  1. 定时进行Eureka client 本地的缓存的可用服务信息列表刷新(即服务获取)
  2. 定时进行Eureka client 的心跳检测(服务续约)
  3. 进行统一的服务注册 (注册)服务信息的同步
  /**
     * eureka 客户端初始化操作 主要是启动如下的定时任务
     * 1、服务获取定时任务  主要实现服务拉取(cacheRefresh 本地服务列表信息的缓存刷新)
     * 2、服务续约(心跳检测)定时任务 每隔指定的时间向eureka server 发送心跳检测
     * 3、服务注册/刷新
     */
    private void initScheduledTasks() {
        int renewalIntervalInSecs;
        int expBackOffBound;
        /**
         * clientConfig 为我们对应的在application,yml中配置的eureka的配置
         * 如果没有配置使用eureka的默认配置
         */
        //从eureka.client.fetch-registry参数设置进来,默认为ture
        if (this.clientConfig.shouldFetchRegistry()) {
            //服务续约的间隔时间 默认30秒
            renewalIntervalInSecs = this.clientConfig
                 .getRegistryFetchIntervalSeconds();
            //10 最大延迟时间的倍数 与服务续约时间相乘的结果作为最大的延迟时间
            expBackOffBound = this.clientConfig.
                getCacheRefreshExecutorExponentialBackOffBound();
            //执行 拉取服务列表的定时任务
            this.scheduler.schedule(
                 new TimedSupervisorTask(
                              "cacheRefresh",
                               this.scheduler,
                               this.cacheRefreshExecutor,
                               renewalIntervalInSecs,
                               TimeUnit.SECONDS,
                               expBackOffBound,
                               new DiscoveryClient.CacheRefreshThread()), 
                               (long)renewalIntervalInSecs, 
                               TimeUnit.SECONDS);
        }

        //从eureka.client.register-with-eureka参数设置进来,默认为ture 是否进行注册
        //如果不需要注册,则该服务并不需要进行服务续约
        if (this.clientConfig.shouldRegisterWithEureka()) {
            //服务续约时间间隔也为30秒
            renewalIntervalInSecs = this.instanceInfo.
                     getLeaseInfo().getRenewalIntervalInSecs();
            //10 最大延迟时间的倍数 与服务续约时间相乘的结果作为最大的延迟时间
            expBackOffBound = this.clientConfig
                   .getHeartbeatExecutorExponentialBackOffBound();
            logger.info("Starting heartbeat executor: 
                        renew interval is: " + renewalIntervalInSecs);
            //服务续约的定时任务
            this.scheduler.schedule(
              new TimedSupervisorTask("heartbeat",
                                 this.scheduler,
                                 this.heartbeatExecutor,
                                 renewalIntervalInSecs,
                                 TimeUnit.SECONDS,
                                 expBackOffBound,
                                 new DiscoveryClient.HeartbeatThread(null)), 
                                 (long)renewalIntervalInSecs,
                                 TimeUnit.SECONDS);

            //创建包含微服务实例的线程任务 InstanceInfoReplicator
            //该类实现了Runable接口,会定时被调用
            //实现服务的注册和eureka server 服务实例信息的刷新
            this.instanceInfoReplicator = new InstanceInfoReplicator(this, 
                           this.instanceInfo,
                           this.clientConfig.
                           getInstanceInfoReplicationIntervalSeconds(), 
                         2);
            //创建服务状态改变的监听器 对于服务状态的改变
           //也需要使用该InstanceInfoReplicator对象进行服务的重新注册/刷新
            this.statusChangeListener = new ApplicationInfoManager
                   .StatusChangeListener() {
                public String getId() {
                    return "statusChangeListener";
                }

                public void notify(StatusChangeEvent statusChangeEvent) {
                    if (InstanceInfo.InstanceStatus.DOWN 
                        != statusChangeEvent.getStatus() && 
                         InstanceInfo.InstanceStatus.DOWN != 
                        statusChangeEvent.getPreviousStatus()) {
                        DiscoveryClient.logger.info(
                        "Saw local status change event {}", statusChangeEvent);
                    } else {
                        DiscoveryClient.logger.warn("Saw local status change event {}", 
 
             statusChangeEvent);
                    }

                    //最终也是调用InstanceInfoReplicator的run()方法
                    DiscoveryClient.this.instanceInfoReplicator.onDemandUpdate();
                }
            };
            //添加时间监听
            if (this.clientConfig.shouldOnDemandUpdateStatusChange()) {
                this.applicationInfoManager
                 .registerStatusChangeListener(this.statusChangeListener);
            }

            //启动服务注册和刷新的定时任务
            this.instanceInfoReplicator.start(this.clientConfig.
                 getInitialInstanceInfoReplicationIntervalSeconds());
        } else {
            logger.info("Not registering with Eureka server per configuration");
        }

    }

     上述代码主要启动了三个定时任务实现 服务信息拉取、服务续约、服务注册三个功能,前两者使用TimedSupervisorTask

    (extends TimeTask 定时任务),主要实现run方法中 "future = this.executor.submit(this.task)" 所有我们的核心关注点是具体的task,后者InstanceInfoReplicator类实现Runnable

第一个定时任务负责从EurekaServer拉取注册信息->new CacheRefreshThread():CacheRefreshThread实现了Runnable,在run()方法中调用了refreshRegistry()方法,这个方法就是去EurekaServer拉取注册信息的方法 主要是使用EurekaHttpClient 提交Http请求来从Eureka Server 获取所有可用服务信息,拉取注册信息分两种:全量获取和增量获取。全量获取会去EurekaServer拉取所有注册的信息,拉取后在本地进行过滤并对服务状态为UP的注册信息进行缓存;增量获取会对拉取下来的注册信息与本地缓存进行合并后进行缓存。

第二个定时任务负责服务心跳/续约->new HeartbeatThread():HeartbeatThread实现了Runnable,在run()方法中调用了renew()方法,当租约不存在时候进行注册操作,也是使用Http请求来与Eureka Server服务端进行心跳

第三个定时任务复制向EurekaServer发送注册/刷新请求->new InstanceInfoReplicator():InstanceInfoReplicator实现了Runnable,在run()方法中向EurekaServer端发起服务注册/刷新请求,这个内容有点不太清楚参考 InstanceInfoReplicator实例详解

4、Eurake 服务实例获取慢的原因

  1. 微服务客户端 延迟注册

Eureka 的服务提供者 在使用eureka client注册到eureka server是延迟注册的

  1. eureka client 每隔指定的时间才从eureka server 中获取获取新的可用服务列表
  2. 服务搭配Ribbon 进行消费的时候 ribbon 也需要隔一定的时候获取服务列表

5、eureka自我保护机制

     通过配置 发现指定时间内服务续约 低于指定的值,会开启自我保护,不会对其注册的服务进行剔除操作,为了避免网络状况不佳的情况。

6、eureka 服务注册中心 是服务调用的核心组件,对于有大量微服务实例注册,所有我们需要构建 高可用,高可靠的eureka集群

7、eureka 设置服务端和客户端应用搭建过程中的问题:

    maven依赖的问题,springBoot的版本和springCloud版本一定要进行匹配

     maven依赖下载 需要设置阿里云仓库

 

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐