上一篇文章Eureka(三)——client注册过程中,我们详述了Eureka Client是如何完成注册、续约、获取服务、下线等操作的。本篇文章,我们来记述Eureka Server是如何工作的。
Eureka基本架构

如果上图所示,Eureka的基本架构由3个角色组成:
Eureka Server
提供服务注册和发现。它们之间会做注册服务的同步,从而保证状态一致
Service Provider
服务提供方,将自身服务注册到Eureka,从而使服务消费方能够找到。它会向Eureka Server做Register(服务注册)、Renew(服务续约)、Cancel(服务下线)等操作
Service Consumer
服务消费方,从Eureka获取服务列表,从而能够消费服务。它会向Eureka Server获取注册服务列表,并消费服务。
Eureka Server启动过程
首先从SpringBoot的入口开始,@EnableEurekaServer的注解如下:
1 | (ElementType.TYPE) |
它导入了另一个配置类EurekaServerMarkerConfiguration,这个类功能上没有任何用处,仅仅是添加了一个Marker bean,用来激活EurekaServerAutoConfiguration。
我们看到在spring-cloud-netflix-eureka-server的spring.factories文件中有如下配置:
1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ |
EurekaServerAutoConfiguraion上加了@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)这样的配置,只有EurekaServerMarkerConfiguration.Marker存在的情况下它才能被激活。
EurekaServerAutoConfiguration
在EurekaServerAutoConfiguration中会完成大部分Bean的新建:
- EurekaServerConfig。服务配置
- EurekaController。一个简单的Controller,负责eureka dashboard的显示,包括Node自身的信息,以及在该节点上保存的注册信息。
- ServerCodecs。提供对json和xml的解析器
- InstanceRegistry。它是注册服务的关键,继承
PeerAwareInstanceRegistryImpl,提供了服务注册、续约、下线等操作。 - PeerEurekaNodes。维护当前Eureka Server要同步的Peer Node,并通过一个定时任务维护Peer Node的信息。
- EurekaServerContext
- EurekaServerBootstrap
- JerseyApplication
- FilterRegistrationBean。把JerseyApplication作为一个Filter,设置最低优先级并过滤Eureka Context下面所有的请求。
EurekaServerInitializerConfiguration
EurekaServerAutoConfiguration通过@Import(EurekaServerInitializerConfiguration.class)导入EurekaServerInitializerConfiguration。
EurekaServerInitializerConfiguration实现了SmartLifecycle接口的start方法,会在所有Spring bean都初始化完成后调用该方法。在start方法中调用EurekaServerBootstrap.contextInitialized来初始化Eureka Server:
EurekaServerBootstrap
EurekaServerInitializerConfiguration的start方法中调用EurekaServerBootstrap的contextInitialized方法:
1 | public void contextInitialized(ServletContext context) { |
initEurekaEnvironment方法中初始化数据中心的信息以及eureka的运行环境initEurekaServerContext方法有如下步骤:- 注册状态转化器,保持向后兼容
- 判断是否是亚马逊的数据中心
- 调用
EurekaServerContextHolder.initialize初始化Eureka Server上下文 - 调用
PeerAwareInstanceRegistryImpl.syncUp从相邻的eureka节点复制注册表。遍历获取到的服务注册信息,调用AbstractInstanceRegistry.register方法将服务注册到本地。 - 调用
PeerAwareInstanceRegistryImpl.openForTraffic设置expectedNumberOfRenewsPerMin和numberOfRenewsPerMinThreshold的值,并将eureka的状态设为UP - 调用
EurekaMonitors.registerAllStats注册所有的监控
PeerAwareInstanceRegistryImpl.syncUp
PeerAwareInstanceRegistryImpl.syncUp方法调用getApplications()从相邻的eureka节点复制注册表。然后遍历获取到的服务注册信息,调用AbstractInstanceRegistry.register方法将服务注册到本地。流程如下:

Eureka Server实现细节
InstanceRegistry
上文我们提高,InstanceRegistry类是注册服务的关键,提供了服务注册、续约、下线等操作。下图是它的继承关系:

我们再来看看Eureka Server几个对外接口的实现
服务注册
服务注册(register)接口接收Eureka Client的注册请求,其流程如下:

ApplicationResource类接收Http请求,调用InstanceRegistry.register方法InstanceRegistry.register方法中记录日志并广播EurekaInstanceRegisteredEvent事件,接着调用父类PeerAwareInstanceRegistryImpl的register方法。PeerAwareInstanceRegistryImpl.register方法有两步:- 调用父类
AbstractInstanceRegistry的register方法注册服务 - 调用
replicateToPeers方法向其他Eureka Server节点(Peer)做状态同步
- 调用父类
AbstractInstanceRegistry.register
AbstractInstanceRegistry.register方法是服务注册最终发生的位置,代码(删除日志代码)如下:
1 | public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) { |
注册的服务列表保存在registry变量中,它是一个嵌套的hashmap:
1 | private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry |
- 第一层hash map的key是app name,也就是服务的应用名字。本例中为
EUREKA-CLIENT - 第二层hash map的key是instance id,也就是实例的id。本例中为
wangqideimac.lan:eureka-client:8762
PeerAwareInstanceRegistryImpl.replicateToPeers
PeerAwareInstanceRegistryImpl.replicateToPeers方法将服务复制到另外的Eureka Server节点上,其主要功能代码如下:
1 | for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) { |
遍历Eureka Server节点(peerEurekaNodes.getPeerEurekaNodes()),排除自身节点,调用replicateInstanceActionsToPeers方法将服务同步到Eureka Server节点。
PeerEurekaNodes
PeerEurekaNodes类中保存Eureka Server的节点,以及相应的其他信息。其获取流程如下:
- EurekaBootStrap.contextInitialized
- EurekaBootStrap.initEurekaServerContext
- DefaultEurekaServerContext.initialize
- PeerEurekaNodes.start
peersUpdateTask
在PeerEurekaNodes.start方法中启动一个周期性的任务来更新Eureka Server的节点:
1 | taskExecutor.scheduleWithFixedDelay( |
更新频率可以通过eureka.server.peerEurekaNodesUpdateIntervalMs来配置
peersUpdateTask是一个线程:
1 | Runnable peersUpdateTask = new Runnable() { |
updatePeerEurekaNodes
peersUpdateTask线程体中调用updatePeerEurekaNodes(resolvePeerUrls())方法更新Eureka Server节点。流程如下:

resolvePeerUrls()方法调用EndpointUtils.getDiscoveryServiceUrls来获取所有Eureka Server节点的地址。该方法默认从配置文件中读取。
updatePeerEurekaNodes方法移除失效的节点,增加新的节点。
PeerAwareInstanceRegistryImpl.replicateInstanceActionsToPeers
PeerAwareInstanceRegistryImpl.replicateInstanceActionsToPeers方法的功能是将服务信息复制到另一个Eureka Server节点中。
它根据节点的不同操作:Cancel、Heartbeat、Register、StatusUpdate、DeleteStatusOverride,调用PeerEurekaNode的不同方法来完成服务信息的复制。这里实际上和Eureka Client对Eureka Server的操作是一样的。
服务续约
服务续约(renew)接口接收Eureka Client的续约请求,其流程如下:

InstanceResource接收服务续约请求,处理方法为renewLease。它调用
InstanceRegistry.renew方法,然后判断renew方法返回的结果:- 如果返回
true:创建一个状态为200的响应返回 - 如果返回
false:说明该服务并没有被注册,创建一个状态为404的响应返回,Client收到这个响应后注册该服务。
- 如果返回
InstanceRegistry.renew方法首先广播EurekaInstanceRenewedEvent事件,然后调用父类PeerAwareInstanceRegistryImpl的renew方法PeerAwareInstanceRegistryImpl.renew方法有两步:- 首先调用父类
AbstractInstanceRegistry的renew方法, - 调用
replicateToPeers方法将服务信息同步到另外的节点上。
- 首先调用父类
AbstractInstanceRegistry.renew
AbstractInstanceRegistry.renew方法是服务续约最终发生的位置,代码(删除日志代码)如下:
1 | public boolean renew(String appName, String id, boolean isReplication) { |
服务下线
服务下线(cancel)接口接收Eureka Client的下线请求,其流程如下:

InstanceResource接收服务下线请求,处理方法为cancelLease。它调用
InstanceRegistry.cancel方法,然后判断cancel方法返回的结果:- 如果返回
true:创建一个状态为200的响应返回 - 如果返回
false:说明该服务并没有被注册,创建一个状态为404的响应返回。
- 如果返回
InstanceRegistry.cancel方法首先广播EurekaInstanceCanceledEvent事件,然后调用父类PeerAwareInstanceRegistryImpl的cancel方法PeerAwareInstanceRegistryImpl.cancel方法有两步:- 首先调用父类
AbstractInstanceRegistry的cancel方法, - 调用
replicateToPeers方法将服务信息同步到另外的节点上。 - 重新计算
expectedNumberOfRenewsPerMin和numberOfRenewsPerMinThreshold
- 首先调用父类
AbstractInstanceRegistry.cancel方法调用InstanceRegistry.internalCancel方法。InstanceRegistry.internalCancel方法首先广播EurekaInstanceCanceledEvent事件,然后调用父类AbstractInstanceRegistry的internalCancel方法
AbstractInstanceRegistry.internalCancel
AbstractInstanceRegistry.internalCancel方法用于下线任务,其流程如下:
- 从服务注册表
registry中将相应的服务删除 - 如果服务注册表中不存在相应的服务,则返回
false - 否则调用
Lease.cancel方法,将evictionTimestamp设置为当前时间。然后返回true
服务获取
服务获取(fetch registries)接口接收Eureka Client的获取服务请求,其流程如下:

- 调用
PeerAwareInstanceRegistryImpl.shouldAllowAccess方法判断是否有权限获取服务信息 - 根据请求生成
Key 使用
Key从ResponseCacheImpl中获取服务信息。ResponseCacheImpl中的readWriteCacheMap是一个LoadingCache类型的变量,其中保存服务信息的缓存。如果readWriteCacheMap中没有保存某个key的值,或者某个key的值失效了,则调用generatePayload方法生成这个key的值。- 返回经过压缩的服务信息。
失效服务剔除
失效服务剔除(Eviction)用来定期在Eureka Server检测失效的服务,检测标准就是超过一定时间没有renew的服务。其流程如下:

失效服务剔除在AbstractInstanceRegistry#EvictionTask类中完成。它的启动流程如下:
- PeerAwareInstanceRegistry.openForTraffic
- AbstractInstanceRegistry.postInit,在postInit中启动EvictionTask,其执行的间隔周期默认为60秒
- EvictionTask代码如下:
1 | public void run() { |
- 首先调用
getCompensationTimeMs()方法获取补偿时间(毫秒) - 然后调用
evict方法检测失效的服务。
getCompensationTimeMs
1 | long getCompensationTimeMs() { |
补偿时间的计算公式为:当前时间 - 最后eviction任务执行时间 - eviction任务的执行间隔
evict
1 | public void evict(long additionalLeaseMs) { |
- 调用
PeerAwareInstanceRegistry.isLeaseExpirationEnabled方法判断是否允许剔除失效服务。
1 |
|
该方法和自我保护机制有关,如果Renews(last min) < Renews threshold,表示Eureka Server进入了自我保护模式,这时Eureka Server不再剔除失效服务。
- 遍历整个服务注册表,调用
Lease.isExpired方法判断租约是否过期,将过期的租约加入expiredLeases
1 | public boolean isExpired(long additionalLeaseMs) { |
计算最大允许清理租约的数量,以及允许清理租约的数量
即使Eureka Server关闭自我保护机制,如果使用
renewalPercentThreshold = 0.85默认配置,结果会是分批逐步过期,举个例子:
1 | // 假设 20 个租约,其中有 10 个租约过期。 |
是否开启自我保护的差别,在于是否执行清理过期租约的逻辑。如果想关闭分批逐步过期,设置renewalPercentThreshold = 0
- 随机清理过期的租约。由于租约是按照应用顺序添加到数据,通过随机的方式,尽量避免单个应用被全部过期。调用
AbstractInstanceRegistry.internalCancel方法下线过期的服务。
总结
Eureka Server中的关键类是InstanceRegistry,在其中完成以下的几个关键任务:
服务注册
调用
AbstractInstanceRegistry.register方法注册服务,再调用replicateToPeers方法向其他Eureka Server节点(Peer)做状态同步。服务列表保存在一个嵌套的ConcurrentHashMap中:
ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry:第一层hash map的key是app name,也就是服务的应用名字,本例中为EUREKA-CLIENT。第二层hash map的key是instance id,也就是实例的id,本例中为wangqideimac.lan:eureka-client:8762服务续约
调用
AbstractInstanceRegistry.renew方法续约服务,再调用replicateToPeers方法向其他Eureka Server节点(Peer)做状态同步。续约的主要操作是更新
registry中保存的服务实例的续约信息(Lease),刷新其中的最近续约时间。服务下线
调用
AbstractInstanceRegistry.cancel方法下线服务(最终调用的是AbstractInstanceRegistry.internalCancel方法),再调用replicateToPeers方法向其他Eureka Server节点(Peer)做状态同步。将相应的服务id从服务注册表
registry中删除。如果服务注册表中不存在指定的服务id则返回false,否则调用Lease.cancel方法将evictionTimestamp设置为当前时间并返回true服务获取
服务获取功能在
ApplicationsResource.getContainers方法中执行。它从缓存ResponseCacheImpl中获取服务信息。ResponseCacheImpl中有一个LoadingCache类型的变量readWriteCacheMap,其中保存服务信息的缓存。同步服务到其他的Eureka Server节点
调用
PeerAwareInstanceRegistryImpl.replicateInstanceActionsToPeers。将服务信息复制到另一个Eureka Server节点节点中。根据服务不同的操作,向Eureka Server发送不同的HTTP请求。
剔除失效服务
剔除失效服务是一个定时任务,它在
AbstractInstanceRegistry#EvictionTask类中完成。其间隔时间在EurekaServerConfig.getEvictionIntervalTimerInMs()中配置,默认为60s。遍历服务列表,将失效的服务实例添加到失效服务列表中(失效服务指的是超过一定时间没有再次续约的服务,该时间由
LeaseInfo.durationInSecs控制。默认为DEFAULT_LEASE_DURATION,即90秒),然后从中随机剔除掉一批失效服务(剔除的量由renewalPercentThreshold控制,默认为0.85)。
http://nobodyiam.com/2016/06/25/dive-into-eureka/
http://blog.didispace.com/spring-cloud-eureka-register-detail/
https://xli1224.github.io/2017/12/31/spring-cloud-eureka-server-analysis/
http://www.iocoder.cn/Eureka/eureka-server-init-second/
https://blog.tookbra.com/2017/08/25/Spring-Cloud-Eureka-Server-Source/index.html
http://www.iocoder.cn/Eureka/instance-registry-evict/