springCloud 注册服务于发现之Eureka

  

一、什么是Eureka?

Eureka架构中的三个核心角色:

  1.服务注册中心

   Eureka的服务端应用,提供服务注册与发现功能,就是刚刚建立的Eureka-Demo

  2.服务提供者

   提供服务的应用,可以是springboot应用,也可以是其他任意技术实现,只要对外

           提供的是Rest风格服务即可。

       3.服务消费者

  消费应用从注册中心获取服务列表,从而得治每个服务方的信息,知道去哪里调用服务方。

Eurka就好比是滴滴,负责管理,记录服务提供着的信息。服务调用者无需自己寻找服务,而是把自己的需求高速Eureka,然后Eureka会把服务你需求的服务告诉你。Eureka说白了就是一个注册服务中心。

同时,服务提供方与Eureka之间通过“心跳机制”进行监控,当某个服务提供方出现问题,Eureka自然会把它从服务列表中剔除。

这样就实现了服务的自动注册,发现,状态监控。

二、原理图是这样的:

 

 

三、 Eureka的工作流程:

  1.服务启动的时候,会生成服务的基本信息对象InstanceInfo,然后再启动的时候会register到服务治理中心。

  2.注册完成后会从服务治理中心拉取所有的服务信息,缓存到本地。

  3.之后服务会每隔30s向注册中心发送一个心跳,续约服务。(这个时间可以自己配置)

  4.如果服务治理中心在90s内没有收到当前这个服务的续约,就会认为这个服务已经挂掉了,会把这个服务的注册信息删掉。

  5.服务停止钱,服务会主动发送一个停止请求,服务治理中心会删除这个服务的信息。

  6.如果Eureka Server收到的心跳包不足正常值的85%(可以进行配置)就会进入自我保护模式,在这个模式下,Eureka Server不会删除任何服务的信息。

四、Eureka服务注册中心的缓存:

    Eureka注册中心有一个Map来保护所有的服务及映射的机器信息。

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

  1.服务注册时,会把服务的信息写到这个registry中

  2.服务从治理中心拉取服务列表信息时,不会把这个registry中拉取,而是从一个ResponseCache中拉取,这样读写分离的原因是为了支持高并发。

而ResponseCache又分为两部分,一个是ReadWriteMap,一个是ReadOnlyMap。

  1.ReadWriteMap的数据时从registry中来的,可以认为是registry的缓存,当服务注册时,除了把信息写到registry中外,还会让ReadWriteMap主动过期,使得会去从registry重新拉取数据。

  2.ReadOnlyMap的数据时从ReadWriteMap中来的,可以认为是ReadWriteMap的缓存(所以它是registry缓存的缓存,也就是双层缓存)当服务需要获取服务列表时,会直接取这个。 ReadOnlyMap的数据,当这个数据不存在时,才会从ReadWriteMap中进行更新。

  3.ReadWriteMap与registry的数据是实时一致的(因为有注册后让ReadWriteMap失效的机制),但是ReadWriteMap与ReadOnlyMap不是实时一致的。

  4.有定时任务会定时从ReadWriteMap同步到ReadOnlyMap,这个时间配置是:

eureka.server.responseCacheUpdateInvervalMs

  5.EurekaServer内部有定时任务,每隔检查过期实例时间,扫描Registry里面过期的实例并删除,并且使用对应的ReadWriteMap缓存失效,这个时间配置是:

eureka.server.eviction-interval-timer-in-ms

五、服务注册的细节:

5.1 客户端做了什么事情?

服务在启动完成后,会启动一个线程,去执行注册信息,具体代码在InstanceInfoRelicator中,这个类实现了Runnable:

   public void run() {
        try {
            discoveryClient.refreshInstanceInfo();

            Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
            if (dirtyTimestamp != null) {
                discoveryClient.register();
                instanceInfo.unsetIsDirty(dirtyTimestamp);
            }
        } catch (Throwable t) {
            logger.warn("There was a problem with the instance info replicator", t);
        } finally {
            Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
            scheduledPeriodicRef.set(next);
        }
    }

在run方法中,会先去检测服务信息是否发生了变更,如果发生了,会调用discoveryClient.registrer();方法重新进行注册,第一次的时候会认为是发生了变更,从而发起第一个注册。

在finally块中,schedule了this,说明会在指定时间后再次执行这个方法。

5.2服务端做了什么事?

处理上面说的registry以及它的缓存对象。

其次会把这次的注册信息添加到一个最近变更的队列中。

private ConcurrentLinkedQueue<RecentlyChangedItem> recentlyChangedQueue =
 new ConcurrentLinkedQueue<RecentlyChangedItem>();

之所以要维护这样的一个队列,是为了更方便服务增量更新服务列表的信息

然后会把注册的信息发送到其他的Erueka节点,发送的方式就是调用其他节点的register方法。

5.3 Eurka Server:注册中心服务端

  注册中心服务端主要对外提供了三个功能:

  5.3.1服务注册
  服务提供者启动时,会通过 Eureka Client 向 Eureka Server 注册信息,Eureka Server 会存储该服务的信息,Eureka Server 内部有二层缓存机制来维护整个注册表

  5.3.2提供注册表
服务消费者在调用服务时,如果 Eureka Client 没有缓存注册表的话,会从 Eureka Server 获取最新的注册表

  5.3.3同步状态
Eureka Client 通过注册、心跳机制和 Eureka Server 同步当前客户端的状态。

  5.4Eureka Client:注册中心客户端

Eureka Client 是一个 Java 客户端,用于简化与 Eureka Server 的交互。Eureka Client 会拉取、更新和缓存 Eureka Server 中的信息。因此当所有的 Eureka Server 节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者,但是当服务有更改的时候会出现信息不一致。

  5.5Register: 服务注册

服务的提供者,将自身注册到注册中心,服务提供者也是一个 Eureka Client。当 Eureka Client 向 Eureka Server 注册时,它提供自身的元数据,比如 IP 地址、端口,运行状况指示符 URL,主页等。

  5.6Renew: 服务续约

Eureka Client 会每隔 30 秒发送一次心跳来续约。 通过续约来告知 Eureka Server 该 Eureka Client 运行正常,没有出现问题。 默认情况下,如果 Eureka Server 在 90 秒内没有收到 Eureka Client 的续约,Server 端会将实例从其注册表中删除,此时间可配置,一般情况不建议更改。

服务续约的两个重要属性

服务续约任务的调用间隔时间,默认为30秒
eureka.instance.lease-renewal-interval-in-seconds=30

服务失效的时间,默认为90秒。
eureka.instance.lease-expiration-duration-in-seconds=90

  5.7Eviction 服务剔除
当 Eureka Client 和 Eureka Server 不再有心跳时,Eureka Server 会将该服务实例从服务注册列表中删除,即服务剔除。

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

DiscoveryManager.getInstance().shutdownComponent();

  5.9GetRegisty: 获取注册列表信息
Eureka Client 从服务器获取注册表信息,并将其缓存在本地。客户端会使用该信息查找其他服务,从而进行远程调用。该注册列表信息定期(每30秒钟)更新一次。每次返回注册列表信息可能与 Eureka Client 的缓存信息不同,Eureka Client 自动处理。

如果由于某种原因导致注册列表信息不能及时匹配,Eureka Client 则会重新获取整个注册表信息。 Eureka Server 缓存注册列表信息,整个注册表以及每个应用程序的信息进行了压缩,压缩内容和没有压缩的内容完全相同。Eureka Client 和 Eureka Server 可以使用 JSON/XML 格式进行通讯。在默认情况下 Eureka Client 使用压缩 JSON 格式来获取注册列表的信息。

获取服务是服务消费者的基础,所以必有两个重要参数需要注意:

# 启用服务消费者从注册中心拉取服务列表的功能
eureka.client.fetch-registry=true

# 设置服务消费者从注册中心拉取服务列表的间隔
eureka.client.registry-fetch-interval-seconds=30

  5.10Remote Call: 远程调用
当 Eureka Client 从注册中心获取到服务提供者信息后,就可以通过 Http 请求调用对应的服务;服务提供者有多个时,Eureka Client 客户端会通过 Ribbon 自动进行负载均衡。

六、服务续约的细节

在服务启动时,会创建一个后台心跳任务,定时去续约服务信息:

            scheduler.schedule(
                    new TimedSupervisorTask(
                            "heartbeat",
                            scheduler,
                            heartbeatExecutor,
                            renewalIntervalInSecs,
                            TimeUnit.SECONDS,
                            expBackOffBound,
                            new HeartbeatThread()
                    ),
                    renewalIntervalInSecs, TimeUnit.SECONDS);

HeartbeatThread就是心跳线程,这里把它包装成了一个TimedSupervisorTask,

注意:scheduler.schedule方法只会在延时一定时间后执行一次提交的任务,所以这里会延时执行,同时,虽然schedule只会执行一次,但是TimedSupervisorTask里封装了循环调用的信息,所以其实是定时调用了。

而HeartbeatThread非常简单:

    private class HeartbeatThread implements Runnable {

        public void run() {
            if (renew()) {
                lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
            }
        }
    }
  • if语句中的renew()方法就是服务续约的方法,里面的逻辑就是向服务治理中心发送了一个http请求。
  • 如果http请求返回的状态码是404,则会认为这个服务没有注册或者注册了但是已经失效,因此会直接调用register()方法进行注册。

七、服务取消的细节

服务停止前会向服务治理中心发一个服务取消的请求,用于注销服务。收到服务注销的请求之后,服务治理中心会做以下操作:

  • 从registry中删除对应的服务信息
  • 使ReadWriteMap缓存失效
  • 将服务取消的信息加入到最近变更队列中

八、自我保护机制

默认情况下,如果 Eureka Server 在一定的 90s 内没有接收到某个微服务实例的心跳,会注销该实例。但是在微服务架构下服务之间通常都是跨进程调用,网络通信往往会面临着各种问题,比如微服务状态正常,网络分区故障,导致此实例被注销。

固定时间内大量实例被注销,可能会严重威胁整个微服务架构的可用性。为了解决这个问题,Eureka 开发了自我保护机制,那么什么是自我保护机制呢?

Eureka Server 在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%,如果低于 85%,Eureka Server 即会进入自我保护机制。

Eureka Server 触发自我保护机制后,页面会出现提示:

 

 

Eureka Server 进入自我保护机制,会出现以下几种情况:
(1 Eureka 不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
(2 Eureka 仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)
(3 当网络稳定时,当前实例新的注册信息会被同步到其它节点中

Eureka 自我保护机制是为了防止误杀服务而提供的一个机制。当个别客户端出现心跳失联时,则认为是客户端的问题,剔除掉客户端;当 Eureka 捕获到大量的心跳失败时,则认为可能是网络问题,进入自我保护机制;当客户端心跳恢复时,Eureka 会自动退出自我保护机制。

如果在保护期内刚好这个服务提供者非正常下线了,此时服务消费者就会拿到一个无效的服务实例,即会调用失败。对于这个问题需要服务消费者端要有一些容错机制,如重试,断路器等。

通过在 Eureka Server 配置如下参数,开启或者关闭保护机制,生产环境建议打开:

eureka.server.enable-self-preservation=true

 

 

posted @ 2020-06-11 00:24  King-DA  阅读(305)  评论(0编辑  收藏  举报