Netflix Eureka 深层解析(下)

Server的工作流程以及通信机制
    server的主要工作是承接client的注册,心跳(renew),状态修改,以及注销(cancel)工作,并且将信息同步。分布式系统中数据从一个点广播到其他点的方式很多,eureka到底使用的是哪种呢?其实这点的实现相当简单,server在扮演着服务注册中心的角色之外,同样也是一个client,它采取和client类似的方式,维护着一组service url,一旦有来自client的注册,心跳等请求,首先要进行本地数据的处理,之后就是同步到其他server节点。(当然eureka还提供了通过DNS解析的另外一种获取service url的方式,但我司并不关心,所以没有过多研究)

    server端的主要逻辑代码集中在PeerEurekaNode,PeerEurekaNodes,AbstractInstanceRegistry和PeerAwareInstanceRegistryImpl等类中。前两个维护了所有对等"peer"的节点,进行新数据的复制工作,后两者的地位类似于client端的DiscoveryClient

    在client启动初始,其实也初始化了一个DiscoveryClient的实例,用其先从其他peer同步信息,因为此时不会有client主动向自己提交请求,所以一些数据来源都是从其他节点主动获取而来。在这里eureka采用了一个很怪异的算法,根据设置的重试次数,不断循环->sleep,最后再将获取的所有instance缓存到本地。我考虑是充分给予DiscoveryClient时间去自动重试。

    注意,在同步数据成功之前,这个server不对外提供服务,理由是也许你会同步到空的或者缺少信息的数据,而如果此时client拉取信息,它会认为第一次full registry fetch成功,之后就执行增量更新了。但对于server来说,主动同步的信息并不作为“增量”来看待。这个道理和client在所有schedule执行之前先full registry fetch一次是一个道理

    另外,service url的配置是可以在运行时更改的,有一个PeerEurekaNodesUpdateIntervalMs配置项就是用来设置在运行时,检查这些对等peer的配置变更的间隔(单从命名来看我还以为是数据同步的间隔)。这种可以在运行时变更的配置有很多,也包括client,在此不做展开

    之后每次server接受到一个client的请求,首先要在本地进行错误检查,并且更新本地缓存,之后如何将复制信息同步到其他peer呢?server(PeerEurekaNode类)维护着两个队列,一个对应的消费者(non-batching)将消息进行单条取出,之后执行。另一个(batching)则定期将数据进行整合,然后一起进行http请求以减少网络消耗同时消费者的逻辑中会加入一定的重试机制,过程复杂但不难想象,所以不再赘述

    其实对于绝大多数操作,使用的都是这个“批量请求”,对应的请求地址是peerreplication/batch/,使用post方法

    复制信息的主动同步请求,每一个http头都会加上x-netflix-discovery-replication:true,代表这是一个复制请求,对于接收复制信息的peer,其处理逻辑与client的请求区别不大,只是会判断如果带有这个头,则不再向其他节点传播

    所以能得知,eureka的数据同步机制就是一个节点轮流将自身信息发送到其他简单的过程,就这么简单

    server会维护所有instance的租约,依旧会通过一个schedule(借助timer和自定义线程实现)定期“驱逐”(evict) 过期的instance。当然这个剔除只要剔除自身的缓存就好了,并不需要进行peer之间同步, 因为理想情况下每个peer都自行进行了驱逐

    在这里说一说增量更新。server在接收到在注册,注销,状态变更(没有心跳)时,会在自身一个队列中记录更新和时间,这个队列会定期将超过3分钟的(可配置)项进行清理。没错,这个队列最终就会作为delta updates 输出的依据。同时在入队列时也会将version加一,就是client获得到的version
    
    每种复制数据同步都有相应的错误处理机制,虽然最终多个数据合并成一个请求,但最终如果出了错误,还是会调用各自的错误处理方法。心跳的复制同步过程稍特殊,但要留到数据一致性分析时候再讲

    client的状态更新请求也会起到心跳的作用

    Eureka的CAP
    在这个时代,分布式系统言必称CAP。而且凡是我看到的eureka相关的说明,虽然内容并不一定有什么意义,但总要和zookeeper和ectd等框架进行对比。一般人们都是这么认为的:
    zookeeper并不适合拿来当服务注册发现的工具,zookeeper满足了CAP中的CP
    eureka才适合,eureka满足了CAP中的AP,微服务体系中就是要重视A

    我基本上同意这个观点。在微服务架构的特殊环境下,服务注册发现中心这一应用有着其特殊性。宏观上讲,这种系统要面对的数据更多是“有”和“没有”的情况,而并非像zookeeper那样,一定多个节点要对某一个数据是1,是2,还是3达成一种共识。通过随机打乱的算法,eureka能做到的就是尽量减少“应该有instance信息却在某个server节点上没有”这种情况,而其他我们能分析到的情况,结合微服务本就该实现的容错/熔断机制,加上netflix提供的一组配套框架,都可以把问题缩到最小
    
    但不同的分布式场景中C的意义不尽相同,不能简单的一句“不能满足强一致性”就了事了。我还是想分析一下eureka在C和A上做的权衡。尤其是一些上面没说的同步机制,可以让eureka趋近于最终一致性
    
    首先你要明确,server的同步和client不同,没有隔离机制,对于某个节点N次重试失败之后,下次新数据需要复制,还是会尝试这个节点
    
    server一端有自我保护机制,会在心跳总量无法维持到某一个阈值时触发,触发的结果就是evict过期instance的任务不再驱逐任何实例。这个阈值的单位是分钟,计算方式是当前所有保存的instance,按照每分钟应该提交2次心跳(30秒心跳),再乘以可以配置的最低能容纳的半分比,而得来。很多地方"2"都是hard code,所以我有疑问,难道心跳间隔不能动态配置么?如果可以,这样计算不就不准确了么?最终我发现ApplicationInfoManager——进行状态管理的类,允许修改这个值,见refreshInstanceInfo()方法。这种方式需要我们自己编程实现,但确实是可以修改一个client的续约间隔时间。这样server计算出来的就会不准确,这算不算一个bug呢?
   
   自我保护机制的目的是为了所在server发生严重的网络分区时,依旧能够提供可用性,但为什么要根据心跳作为依据?通过上面的分析我们知道eureka server集群本身是基于http的,无法维持一个持久的状态,在整个系统的网络通信中,在client到server, peer到peer之间,心跳信息应该远大于其他信息的传输量。那虽然peer之间并不根据彼此的心跳(自身也是client)做什么逻辑判断,但其下的client的心跳复制数据本身也足够作为判断分区的依据了。而在一个稳定理想的集群中,心跳的信息绝大多数应该是其他peer复制过来的,如果达到一定阈值,更多的可能性不是server和client发生分区,而是peer和peer之间发生分区,但本身client并没有真的down掉。所以才有这种自我保护的触发机制—— 更高的概率是client可用,为什么要将其驱逐呢?
   
    心跳的复制信息(专指peer到peer)在eureka系统中还起到了检查数据一致性的作用,是通过如下机制实现:
  • A向B同步client C的心跳信息,B返回404,这里并不一定是B本地没有C的注册信息,也可能是B中保存的lastDirtyTimestamp比较小(靠前)。A看到返回404则马上再进行一次注册的请求发给B,把最新的C的信息同步过去,当然也包括lastDirtyTimestamp
  • A向B同步client C的心跳信息,B返回409,代表正相反,B保存的lastDirtyTimestamp比较大(靠后),不同意采纳A的信息而是将自身的C的信息放在消息体里,返回给B。B看到409则将消息体内的C的信息更新到本地,当然也包括lastDirtyTimestamp
    这个机制可以通过配置关闭

    lastDirtyTimestamp是我在前面没提过的概念,但很重要,初始值为当前时间,client在状态更新时,在运行时元数据变更时,lastDirtyTimestamp会更新,在每个http请求时,query的param都会加上lastDirtyTimestamp,可见其重要性
    
    在发生网络分区期间,不同的server A和B可能针对client C产生了不同的数据,通过这种互相同步纠错,可以最终趋近于最终一致性。

    但lastDirtyTimestamp的来源是真实的时间戳,分布式系统中往往可能发生时钟漂移的问题,一般在使用timestamp时会有一个简单的自我比较,但我并没有在eureka源代码中看到
    
    overridden status 
    这部分功能在服务端“出镜率”很高,但我并没有弄明白。每个instance的信息中就包含了overridden status。现在我能弄明白的一点是,这个功能在管理员进行“强行”状态更新时使用。overridden status会随着复制信息传播,在一个git 的issue 上有讨论相关内容,但并没看懂,争取以后填坑吧
展开阅读全文

没有更多推荐了,返回首页