我是靠谱客的博主 内向日记本,最近开发中收集的这篇文章主要介绍Netflix Eureka 深层解析(上),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

前言

    能看到这篇文章点击进来,博主就当你了解Netflix Eureka的基本概念,也把官方的wiki看了几遍了eureka wiki,所以我不再赘述Eureka要怎么用。我写这篇文章主要目的还是留一个文档,所以这里不会有太入门的东西。由于我司现在正在构建微服务的体系,也在使用Eureka作为服务注册和发现的框架,但工作过程中深感官方和非官方文档太少,我们不能把一个对其一无所知的框架搬上生产环境,更何况是整个微服务体系中的重要一环,幸好Eureka的源代码量不大。我对这篇文章中得来的结论,主要还是通过阅读源代码,加上少部分的实践中的抓包,并不保证每一个结论都严格经过验证,这姑且算作一个免责声明吧。


    虽然我司最终部署的Eureka是经过Spring Cloud改造过的,但我阅读的源码却是原始的Netflix Eureka的版本。

    本文会先深入分析以下问题,让大家对Eureka的机制有一定了解,在最后再简单的列出阅读源代码的一些关键点:

1. Eureka系统中有哪几种主要的模型?

2. Eureka系统中不同模型之间是如何通信的?

3. 为什么说Eureka系统选择了CAP中的AP,为什么要这么选,Eureka系统能将一致性保证到什么程度?

4. Eureka wiki中提到的增量更新(delta updates)是什么概念, 为什么要这么设计?

5. Eureka wiki中提到的自我保护机制(self-preservation mode)是什么概念,为什么要这么设计?


Eureka系统中主要的模型

    如果从对待服务的角度将,Eureka系统可以分为三种角色:服务发现者,服务注册者,和注册发现中心。但这三种角色并不和实际部署的模型是一对一的关系。我在下文所称呼的server,主要承担注册发现中心的职责,但如果有其他同样的server作为一个注册发现的集群,那它们同时也都具有服务发现和注册的角色。

    就算是一个系统中的微服务,也可能分别或者同时具有发现和注册的角色,但我们不用单独分析只具有一种身份的情况,所以在下文我简单的称呼一个注册发现中心的实例为server,一个注册自身,发现其他服务的微服务为client,当站在集群角度分析server之间的关系时,称呼他们peer,而client注册的信息,则称呼实例或者instance, 这些也是遵从Eureka本身的命名规则。


基于Http(s)协议的框架

    所有网络通信都是基于http(s)协议的,这是一个大前提。牢记这一点,对后面的分析很有用


基于AWS的框架

    Eureka和AWS是紧密结合的,无论是配置还源码,你都躲不开一些AWS的概念,比如Region,Zone(或者AZ - Availability Zone), ASG。不幸的是我司并不使用AWS,而是Azure。所以我在阅读源码时尽量不纠缠于这些概念,本文也不会对这些机制的影响做深入讨论。我个人猜测Region更接近数据中心的概念,而Zone更类似于物理距离上比较接近,很容易一起发生故障(例如断电)的区域,比如同一个机房甚至机架。

    Region可以通过配置文件进行配置,如果不配置,会默认使用us-east-1。同样Zone也可以配置,如果不配置,会默认使用defaultZone。

    client在配置文件中查找server的url时,会优先查找自身所在zone的配置项eureka.serviceUrl.<my zone>,但我们往往不这么配置,而是直接使用eureka.serviceUrl.default,这是因为<my zone>的配置如果找不到回去找eureka.serviceUrl.default的配置。


Client的地址选取

我们知道client会在后面的生命周期中注册,保持心跳,更新状态信息以及注销自身。在装载所有server url之后,client会打乱顺序,并且将自身zone的所在的url排到数组的前面,每一次进行网络请求,都选取第一个地址,如果失败就顺次尝试。

同时client有一种隔离机制,对于网络通信错误的url,会进入隔离区,下次进行通信不会使用。但这种隔离并不是永久的,当达到某一个阈值后,这个隔离区会清空。

Instance的状态

    一个对外提供服务的instance,对应不同的生命周期,被划分为五种状态:

  • Up
  • Down
  • Starting
  • Out_Of_Service
  • Unknown
    一个刚启动的instance状态是unknown。up代表可以接受流量,server看到这个状态的instance,才可以将其信息返回给其他服务发现者。starting代表正在启动的过程,往往是已经注册上但还没准备好接受流量。out of service代表一种暂停对外服务的概念,应用在运维部署阶段。down则代表一种完全不提供服务的状态,这个状态是从客户端角度触发的,因为如果一个客户端的状态转换成down了,接下来要做的就是向server注销,所以server基本不可能出现down状态的信息

Client的通信机制

    client之间并不通信,所有的网络请求都是与server之间进行的,主要分为以下几点:

  1. 启动后首先注册自身
  2. 一旦有变更则提交新信息
  3. 30秒一次的心跳信息
  4. 在shutdown时候注销

    client的核心代码实现都在DiscoveryClient类中,需要重点看。在DiscoveryClient的构造函数中,进行了几件主要的工作:

  • 获取所有注册在server的实例的信息(full registry fetch)
  • 开始心跳的schedule
  • 开始注册自身信息
  • 开始获取增量更新数据的schedule。
    获取所有实例的信息是优先于其他行为的,这是为了保证client本地保证完整正确的信息。之后三个步骤实际上都是后台运行的schedule,间隔也可以配置。注册自身信息的功能在概念上,和状态/信息更新都统一抽象成了"注册"的概念,同样是一个后台线程定时检查本地数据是否"dirty"了,如果是,则向server提交更新。当然其也提供及时更新的功能,但只在instance status发生改变时触发
    从上面的表述可以发现,两个向server提交信息的请求:第一次注册,心跳,并不能保证注册先执行,所以会发生心跳返回http status 404的结果,即无法找到对应的app和instance id的信息,这时候client会进行一次注册。即,心跳可能先于默认的注册schedule触发一次注册
    同时整个微服务的状态不可能一直处于稳定状态,所以client要定时从server获取新的数据,将本地更新

  • 心跳会向/apps/<appname>/<instance id>地址提交put请求,但消息体没有内容
  • client的注册和状态更新会向/apps/<appname>提交post请求,消息体内是自身信息(似乎不太合理,post并不应该具有幂等的意义)
  • 注销会向/apps/<appname>/<instance id>地址提交delete请求
  • 全部获取会向/apps地址提交get请求,增量更新回向/apps/delta提交get请求

    这里又引出来一个增量更新的概念,eureka wiki 并没有太多解释。从client一端的表现来看,其机制如下:

除非某些条件触发(比如第一次获取),从server获取instance信息都是执行增量获取,即从某一段时间之后所有的发生变更的信息,这个“某一段时间”从wiki上得知是“about 3 min”

为了标示变更状态,系统引入了action type概念,包含added, modified, deleted三种状态,client需要根据这三种状态来更新本地缓存的instance对应信息

增量更新还有一个版本号的概念,server端保证只有注册的实例信息发生变化时,这个版本号才变化,并且单调递增。在每次进行delta updates信息获取时,这个版本号包含在返回消息体中,client更新完本地instance缓存也要更新版本号缓存。我开始以为这个版本号可以简单的解决网络延迟带来的旧数据把新数据覆盖掉的问题,但发现client只是简单的覆盖此值,并不进行比较,后来了解server数据复制机制后,也明白这种方法并不能解决数据覆盖问题(我并没有找到这种问题的保障机制)

    简单的举个例子方便理解,假如某一时刻A节点接入server,他进行全部获取,得到的信息是B,C三个节点。之后如果B由于某种原因down掉,而新节点D接入,再次进行增量更新,A获取到的信息将是B的action type为deleted,C的action type为added,还有A自己的added,而C呢?C没有变化,但也有可能获取到它的信息,这要看它上次变更是否超过了3分钟,所以你就理解,每个节点的变更状态都是单独计时

    具体增量更新的实现细节我们到了server的部分再说

    除了所有的instance信息,以及版本号以外,同步内容中还会包含一个hash code,client会根据instance信息计算出hash code,并进行对比,如果出现不一致则认为同步出了问题。eureka wiki中称这个过程是“reconcile”。hash code的计算规则很简单,就是将所有instance按照状态分组,计算每组instance的数量,最后拼成一个字符串。这段代码可以自行去查看。这里我有一个疑问没有答案,就是这个reconcile的过程是为了解决什么问题?


最后

以上就是内向日记本为你收集整理的Netflix Eureka 深层解析(上)的全部内容,希望文章能够帮你解决Netflix Eureka 深层解析(上)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(59)

评论列表共有 0 条评论

立即
投稿
返回
顶部