基础概念

  • 服务注册 Register:当 Eureka 客户端向 Eureka Server 注册时,它提供自身的元数据,比如IP地址、端口,运行状况指示符URL,主页等。
  • 服务续约 RenewEureka 客户会每隔30秒(默认情况下)发送一次心跳来续约。 通过续约来告知 Eureka ServerEureka 客户仍然存在,没有出现问题。 正常情况下,如果 Eureka Server 在90秒没有收到 Eureka 客户的续约,它会将实例从其注册表中删除。

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

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

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

Eureka架构

image.png
蓝色的 Eureka ServerEureka 服务器,这三个代表的是集群,而且他们是去中心化的。
绿色的 Application ClientEureka 客户端,其中可以是消费者提供者,最左边的就是典型的提供者,它需要向 Eureka 服务器注册自己和发送心跳包进行续约,而其他消费者则通过 Eureka 服务器来获取提供者的信息以调用他们

Eureka服务停

image.png

Eureka的基本工作流程

1、Eureka Server 启动成功,等待服务端注册。在启动过程中如果配置了集群,集群之间定时通过 Replicate 同步注册表,每个 Eureka Server 都存在独立完整的服务注册表信息

2、Eureka Client 启动时根据配置的Eureka Server地址去注册中心注册服务。
3、Eureka Client 会每 30s 向 Eureka Server 发送一次心跳请求,证明客户端服务正常
4、当 Eureka Server 90s 内没有收到 Eureka Client 的心跳,注册中心则认为该节点失效,会注销该实例
5、单位时间内 Eureka Server 统计到有大量的 Eureka Client 没有上送心跳,则认为可能为网络异常,进入自我保护机制,不再剔除没有上送心跳的客户端
6、当 Eureka Client 心跳请求恢复正常之后,Eureka Server 自动退出自我保护模式
7、Eureka Client 定时全量或者增量从注册中心获取服务注册表,并且将获取到的信息缓存到本地
8、服务调用时,Eureka Client 会先从本地缓存找寻调取的服务。如果获取不到,先从注册中心刷新注册表,再同步到本地缓存
9、Eureka Client 获取到目标服务器信息,发起服务调用
10、Eureka Client 程序关闭时向 Eureka Server 发送取消请求,Eureka Server 将实例从注册表中删除

Eureka的缓存流程

Eureka Server数据存储

image.png

我们知道 Eureka Server 在运行期间就是一个普通的 Java 项目,并没有使用数据库之类的存储软件,那么在运行期间是如何存储数据的呢?

Eureka Server的数据存储分为了两层: 数据存储层和缓存层。 数据存储层记录注册到Eureka Server上的服务信息,缓存层是经过包装后的数据,可以直接在Eureka Client调用时返回。我们先来看看数据存储层的数据结构。

Eureka Server 的数据存储层是双层的 ConcurrentHashMap,我们知道 ConcurrentHashMap 是线程安全高效的 Map 集合。

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

第一层的 ConcurrentHashMap 的 key=spring.application.name 也就是客户端实例注册的应用名;value 为嵌套的 ConcurrentHashMap。
第二层嵌套的 ConcurrentHashMap 的 key=instanceId 也就是服务的唯一实例 ID,value 为 Lease 对象,Lease 对象存储着这个实例的所有注册信息,包括 ip 、端口、属性等。

根据这个存储结构我们可以发现,Eureka Server 第一层都是存储着所有的服务名,以及服务名对应的实例信息,也就是说第一层都是按照服务应用名这个维度来切分存储:

  1. 应用名1:应用1实例 Map
  2. 应该名2:应用2实例 Map
  3. ...

第二层是根据实例的唯一 ID 来存储的,那么按照这个结构最终的存储数据格式为:

  1. 应用1实例A:实例A的注册信息
  2. 应用名1:应用1实例: 应用1实例B:实例B的注册信息
  3. 应用1实例C:实例C的注册信息
  4. ....
  5. -----------------
  6. 应用2实例F:实例F的注册信息
  7. 应该名2:应用2实例: 应用2实例G:实例G的注册信息
  8. ...
  9. ...

Eureka的简介 - 图4
当如服务的状态发生变更时,会同步Eureka Server中的registry数据信息,比如服务注册,剔除服务时。

Eureka Server缓存机制

Eureka Server为了提供响应效率,提供了两层的缓存结构,将Eureka Client所需要的注册信息,直接存储在缓存结构中。

第一层缓存: readOnlyCacheMap,本质上是ConcurrentHashMap , 依赖定时 从readWriteCacheMap同步数据,默认时间为30秒。

readOnlyCacheMap :是一个CureentHashMap只读缓存,这个主要是为了供客户端获取注册信息时使用,其缓存更新,依赖于定时器的更新,通过和readWriteCacheMap的值做对比,如果数据不一致,则以readWriteCacheMap的数据为准。

第二层缓存: readWriteCacheMap, 本质上是Guava缓存

readWriteCacheMap:readWriteCacheMap的数据主要同步于存储层。当获取缓存时判断缓存中是否没有数据,如果不存在此数据,则通过CacheLoader的load方法去加载,加载成功之后将数据放入缓存,同时返回数据。

readWriteCacheMap 缓存过期时间,默认为180秒,当服务下线、过期、注册、状态变更,都会来清除此缓存中的数据。

Eureka Client 获取全量或者增量的数据时,会先从一级缓存中获取;如果一级缓存中不存在,再从二级缓存中获取;如果二级缓存也不存在,这时候先将存储层的数据同步到缓存中,再从缓存中获取。

通过Eureka Server的二层缓存机制,可以非常有效地提升Eureka Server的响应时间,通过数据存储层和缓存层的数据切割,根据使用场景来提供不同的数据支持

其它缓存设计

通过Eureka Server端存在缓存外,Eureka Client也同样存在着缓存机制,Eureka Client启动时会全量拉取服务列表,启动后每隔30s从Eureka Server量获取服务列表信息,并保持在本地缓存中。

Eureka Client 增量拉取失败,或者增量拉取之后对比 hashcode 发现不一致,就会执行全量拉取,这样避免了网络某时段分片带来的问题,同样会更新到本地缓存。

同时对于服务调用,如果涉及到 ribbon 负载均衡,那么 ribbon 对于这个实例列表也有自己的缓存,这个缓存定时(默认30秒)从 Eureka Client 的缓存更新。
这么多的缓存机制可能就会造成一些问题,一个服务启动后可能最长需要 90s 才能被其它服务感知到:

1、首先,Eureka Server 维护每 30s 更新的响应缓存
2、Eureka Client 对已经获取到的注册信息也做了 30s 缓存
3、负载均衡组件 Ribbon 也有 30s 缓存
这三个缓存加起来,就有可能导致服务注册最长延迟 90s ,这个需要我们在特殊业务场景中注意其产生的影响。

个人总结:

新进一个服务会注册进Eureka Server中的注册表中,然后情况读写缓存,定时器同步读写缓存和只读缓存,默认时间为30s。 eureka-client也会30s获取一次注册表中的信息同步到本地缓存。