Euraka-看这就够了

本文详细介绍了Eureka作为服务注册与发现组件的工作原理,包括多副本机制、服务注册与续约流程、自我保护机制、服务端与客户端的实现细节,以及与Zookeeper的区别。Eureka在面对网络故障时,通过自我保护模式避免服务误删除,保障服务的可用性,更倾向于AP原则,而非Zookeeper的CP保证。
摘要由CSDN通过智能技术生成

1.架构图

 

架构图.png

  • Euraka支持多副本机制

  • 所有的Replicate互相保持数据(已注册的服务列表)同步

  • 客户端可以保持与Replicate通信

2.基本流程

  1. 服务提供者启动服务,构建InstanceInfo对象,将此对象信息注册至服务中心

  2. 注册完成后会从注册中心拉取所有的服务列表缓存至本地

  3. 注册中心每隔30秒向服务提供者发送心跳,判断服务提供者是否存活, 同时进行续约服务

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

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

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

3.自我保护机制

在默认配置中,Eureka Server在默认90s没有得到客户端的心跳,则注销该实例,但是往往因为微服务跨进程调用,网络通信往往会面临着各种问题,比如微服务状态正常,但是因为网络分区故障时,Eureka Server注销服务实例则会让大部分微服务不可用,这很危险,因为服务明明没有问题。

为了解决这个问题,Eureka 有自我保护机制,通过在Eureka Server配置如下参数,可启动保护机制

 

eureka.server.enable-self-preservation=true

当Eureka Server节点在短时间内丢失过多的客户端时(可能发送了网络故障),那么这个节点将进入自我保护模式,不再注销任何微服务,当网络故障回复后,该节点会自动退出自我保护模式。
服务过期时间配置:

 

eureka.instance.lease-expiration-duration-in-seconds

服务刷新时间配置:

 

eureka.instance.lease-renewal-interval-in-seconds

自我保护模式的架构哲学是宁可放过一个,决不可错杀一千

4. 服务端

 

private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
            = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
  • 服务注册时,会把服务的信息写到这个registry中

  • 服务从治理中心拉取服务列表信息时,不会从这个registry中拉取,而是从一个ResponseCache中拉取,这样读写分离的原因应该是为了支持高并发。
    而ResponseCache又分为了两个部分,一个是ReadWriteMap,一个是ReadOnlyMap。

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

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

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

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

 

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

 

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

5.客户端

服务在启动完成后,会启动一个线程,去执行注册信息,具体代码在InstanceInfoReplicator中,这个类实现了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.register();方法重新注册,第一次的时候也会认为是发生了变更,从而发起第一次注册。
  • 在finally块中,schedule了this,说明会在指定时间后再次执行这个方法

服务端对应处理过程

  • 首先会处理上面所说的registry以及它的缓存对象。
  • 其次会把这次的注册信息添加到一个“最近变更队列中”

 

private ConcurrentLinkedQueue<RecentlyChangedItem> recentlyChangedQueue =
 new ConcurrentLinkedQueue<RecentlyChangedItem>();
  • 之所以要维护一个这样的队列,是为了方便服务增量更新服务列表的信息。

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

6.续约

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

 

   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缓存失效
  • 将服务取消的信息加入到最近变更队列中

7.与zookeeper异同

著名的CAP理论指出,一个分布式系统不可能同时满足C(一致性)、A(可用性)和P(分区容错性)。由于分区容错性在是分布式系统中必须要保证的,因此我们只能在A和C之间进行权衡。在此Zookeeper保证的是CP, 而Eureka则是AP。

8.1 Zookeeper保证CP

当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但是zk会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30 ~ 120s, 且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得zk集群失去master节点是较大概率会发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。

8.2 Eureka保证AP

Eureka看明白了这一点,因此在设计时就优先保证可用性。Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:

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

因此, Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使整个注册服务瘫痪。

9. 代码

源码地址:https://github.com/spring-cloud/spring-cloud-netflix



作者:life_zl
链接:https://www.jianshu.com/p/6a3db6939fb0
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值