16. SpringCloud之Eureka服务端源码解析

2022-04-20  本文已影响0人  天还下着毛毛雨
image.png

1、前言

上一篇讲到, eureka客户端 向服务端,注册,拉取服务列表,发送给心跳 都需要 调 服务端的接口 :

那么看eureka服务端的源码,我们就从 这几个接口开始看。

eureka 服务端是通过 JAX-RS 规范接收客户端的请求的。

上述接口都在 这个 com.netflix.eureka:eureka-core:1.9.22 包里的 ApplicationsResource类和ApplicationResource类 中。

image.png

2、服务注册接口

服务注册接口是 : /apps/{appId} , POST请求。

会被由这个方法接受请求

image.png

返回了一个 ApplicationResource 对象。ApplicationResource类同样是用来接受请求的

image.png

由于服务注册是POST请求,就会调到该类下 有@POST注解的addInstance 方法,入参为 eureka客户端 传过来的 用于注册的 实例信息

image.png

registry 对象的类型是PeerAwareInstanceRegistryImpl, 这个类把客户端的实例注册到本地服务列表之后,还会 向其他的eureka集群里的其他机器同步 注册的客户端的信息。

image.png

2.1、服务注册

先看服务注册的代码

PeerAwareInstanceRegistryImpl 有个抽象父类 AbstractInstanceRegistry, 注册方法就会调用 AbstractInstanceRegistry.register方法。

image.png

2.1.1、registry对象

这里的registry 就是 用来存所有的服务列表的,是个ConcurrentHashMap

image.png

key 是 应用名称, value是服务列表 , 又是一个Map(map的多个entry对应着 同一个 服务名称的 多台实例), 这个map 的key是 appId,value 则是 Lease对象。

Lease对象可以理解成 客户端实例信息的 包装类

image.png

最终的registry对象的结构是这样的

image.png

2.1.2、注册方法整体逻辑

先从registry 里根据应用名称取 map。

如果取不到,新建一个ConcurrentHashMap<String, Lease<InstanceInfo>> 这样的map对象,设置到registry对象里,key为应用名称 。

如果可以取到就直接返回 registry 里 该应用名称对应的map。

最终返回这个map对象。

image.png

得到这个map对象之后(这个map对应已经存到registry 里了)。

根据 appId 从这个map里 取出Lease对象,判断是否需要替换, 这个不重要,跳过。

image.png

最后,新建一个 包装有 registrant 客户端实例信息 和 没有收到客户端心跳就剔除该服务的时间间隔(如何客户端没有配置,则用服务端默认的90秒) Lease对象。

最终 key 为客户端实例id, value为Lease对象, put到map里。

image.png

到此,这个客户端,已经注册 到了 服务端的 服务列表里了。

2.2、复制 eureka 的节点信息到其他节点

上面代码把 服务注册到服务端本地的服务列表里之后,下面那行代码就到 要把 服务列表 复制给 集群里的其他节点(如果 配置了eureka注册中心集群的话.)

image.png

peerEurekaNodes.getPeerEurekaNodes() 存有 集群里所有的eureka服务端节点, 遍历每一个, 如果不是当前节点, 则 开始 复制 当前注册的客户端信息 给 这个节点。

image.png

action是注册,走这

image.png image.png

replicationClient 对象封装了 集群实例之间的调用方法,调到父类的register方法。

最终 肯定也是 调这个eureka 服务端节点的 注册接口,传入 注册的客户端实例,注册到这个eureka 服务端节点上。

image.png

遍历完集群内所有其他的 eureka 服务端节点 之后, 就完成了当前注册的服务 在集群间的复制。

3、接受心跳接口

接受心跳的 接口 会调到 ApplicationResource类里, 有这个 @Path("{id}") 注解的方法里,返回InstanceResource对象。

image.png

InstanceResource 类仍然 是 用来接受请求的

image.png

如果发送心跳是 PUT请求,调到InstanceResource 类 有@PUT注解的renewLease方法上。

image.png

再调用InstanceRegistry.registry 对象的renew方法,进行 服务的续约保活。

image.png

又调到父类的renew方法

这里有两个动作, 一是在本地服务列表里 保存该服务的 心跳信息 super.renew, 二是保存心跳成功,同步 该服务的心跳信息给集群内其他节点 replicateToPeers。

image.png

3.1、保存心跳信息

保存心跳信息又调到 registry对象的 抽象父类 AbstractInstanceRegistry的renew方法

从本地服务列表里,根据应用名称取出 服务列表 Map<String, Lease<InstanceInfo>>独享, 再根据 实例id 取出 Lease对象。

image.png

最后调用 Lease 对象的renew方法,

image.png

其实就是 更新了一下 这个服务 下次处理心跳的 最后期限。


image.png

到此, 客户端发送心跳,服务端保存心跳, 服务续约成功。

3.2、复制心跳给eureke服务集群内其他节点

保存成功 发送心跳的服务的 心跳信息之后, 下一步 是复制给其他节点。操作类型是Heartbeat。

image.png

这代码和 服务注册 一样,遍历每一个 非自身的其他节点对象

image.png

发心跳信息


image.png image.png

调 replicationClient的 sendHeartBeat方法, 不用多说肯定也是调用这个节点 发送心跳的接口。将 刚才发送心跳 给自己的 服务 的实例信息(包含状态)传过去,PUT请求

image.png

遍历完集群内所有其他的 eureka 服务端节点 之后, 就完成了 这个服务的心跳信息 在集群间的复制。

4、拉取服务列表接口

会调到 ApplicationsResource 类里 有 @GET 的方法

image.png

上面会对 返回值进行 zip压缩,我们看下 未压缩之后的 返回结果是啥

image.png

抠出来 json格式化一下看, 就是返回的所有的服务列表。

image.png

最终客户端接受到完整的服务列表之后, 更新 客户端本地的服务列表, 完成服务的发现。

5、过期服务剔除与自我保护

前面接受心跳的接口里, InstanceResource类 里会有一个 registry类的对象, 来专门进行 发送心跳的服务的 心跳信息保存。

registry类 有个抽象父类AbstractInstanceRegistry ,里面有一个 定时器和 任务 就专门就来检查 服务列表里的服务 是不是需要被剔除。

image.png

5.1、定时器的启动

这个定时器启动的话还是利用的Spring生命周期,有这么一个类 EurekaServerInitializerConfiguration,实现SmartLifecycle 接口, 并有@Configuration注解

那么这个类会被 注册到Spring容器里,并且在 Spring容器加载完成之后,会调用这个类实例 实现至SmartLifecycle接口的start方法.

在start的方法里 会一步一步的调到AbstractInstanceRegistry.postInit来启动这个定时器。感兴趣的的可以自己打断点看一下调用链,这里就不再赘述了。

image.png

会启动evictionTimer 定时器,每隔serverConfig.getEvictionIntervalTimerInMs() 默认60s 执行一次EvictionTask 的run方法

serverConfig.getEvictionIntervalTimerInMs()对应的就是这个配置

# 移除失效服务的时间间隔单位毫秒
eureka.server.eviction-interval-timer-in-ms=60000

5.2、过期服务剔除

所以,所有服务剔除的 逻辑都在 提交的 EvictionTask 任务里。

这个类肯定实现 Runnable接口,直接看run方法

调用了evict方法, 传入 了 compensationTimeMs参数,这个时间 值基本为0

这两个 代码执行的时间间隔基本为0, 减去 60s,等于负数,最终<0 ,返回0

image.png

5.2.1、是否触发自我保护

evict方法 一开始会判断 是否 自我保护是否可用

image.png

如果统计的最近一分钟收到心跳的次数, 是小于 每分钟期望收到心跳次数的阈值,并且开启了自我保护,那么就会返回false。

image.png

那么外面的判断结果就为true, 直接return出去, 保护服务列表里剩下的服务。

这是为了 防止 这台 eureka服务端 由于网络故障,导致 无法正常处理 服务的心跳,然后导致大量服务被移除。

5.2.1.1、每分钟接受心跳次数的阈值的更新

这个numberOfRenewsPerMinThreshold 每分钟接受心跳的阈值 是通过一个定时器来实现的。

父类 PeerAwareInstanceRegistryImpl 里有一个Timer定时器变量

image.png

会以15分钟为周期, 执行一次 run方法

这个15分钟的周期 就是对应的这个配置, 默认是15分钟。

eureka.server.renewalThresholdUpdateIntervalMs=15*60*1000
image.png

run方法里,会执行 updateRenewalThreshold()

先计算所有服务的实例个数, 每个服务发送心跳的时间为 30s,那么每分钟期望接受到的心跳个数为 实例个数 * 2,每分钟 接受心跳的最小阈值 是每分钟期望接受到的心跳个数 * 0.85。

image.png

5.2.1.2、总结

通过源码知道,自我保护的功能 和我平时理解的不一样。

是以每15分钟为临界点, 根据当前的服务总数 * 2(一分钟2次心跳) * 受保护的服务占比0.85 , 计算出 每分钟 期望收到的心跳次数的阈值, 如果开启了自我保护配置 并且最近一分钟收到的心跳次数 < 每分钟 期望收到的心跳次数的阈值 ,那么 将 触发自我保护,不移除任何服务。

是以每分钟 进行 统计的,而不是笼统的 在15分钟内超过85% 这种说法。

5.2.2、收集需要过期的服务

如果没有达到自我保护的阈值, 就开始遍历 所有的服务列表

image.png

判断是不是过期,这个过期其实就是 多长时间没有收到心跳了。

看看当前时间是不是 > (上次 接受此服务心跳的时间(这个每次服务端接到心跳都会更新 ),加间隔 ,默认90秒), 这就说明,eureka服务端 已经超过 90s 没有接受到 这个服务的心跳了。是需要移除的。

image.png

符合被移除的条件,放到需要移除的实例 集合里。

5.2.3、防止一次性移除过多的实例

收集到被移除的服务集合之后, 这里会根据 受保护实例的 比例 阈值 (默认0.85) 来判断 要移除多少个。

也就是说 除了受保护的85%的服务之外, 其他15%数量的服务是可以被移除的。

image.png

这个判断是 为了防止 一次性从服务列表里 剔除 太多的服务,直接把存活的服务降到 受保护的服务占比 85%以下。每次最多剔除 15%的过期 服务。

即使是 没有开启自我保护功能的情况下,大量的服务瘫痪了(有可能是短暂的网络故障),也不会一次性剔除所有瘫痪的服务, 而是每次最多剔除 15%的过期 服务 。这样会更平滑点,也给 因为诸如网络故障这种短暂的异常导致 心跳不正常的服务 提供了恢复的缓冲时间(下一个60s 之前恢复好,就一切照旧,不用重新注册了)。

5.2.4、移除过期服务

最终 根据要被移除的数量,遍历每一个,从服务列表里移除

image.png image.png
上一篇下一篇

猜你喜欢

热点阅读