Eureka服务注册中心
关于服务注册中心
服务注册中心本质上是为了解耦服务提供者和服务消费者。
对于任何一个微服务,原则上都应存在或者支持多个提供者(比如一个微服务部署多个实例),这是由微服务的分布式属性决定的。
更进一步,为了支持弹性扩缩容特性,一个微服务的提供者的数量和分布往往是动态变化的,也是无法预先确定的。因此,原本在单体应用阶段常用的静态LB(负载均衡)机制(就是在配置文件中写死服务实例的列表信息,负载均衡策略从这个配置文件中读取)就不再适用了,需要引入额外的组件来管理微服务提供者的注册与发现,而这个组件就是服务注册中心。
服务注册中心一般原理

分布式微服务架构中,服务注册中心用于存储服务提供者地址信息、服务发布相关的属性信息,消费者通过主动查询和被动通知的方式获取服务提供者的地址信息,而不再需要通过硬编码方式得到提供者的地址信息。消费者只需要知道当前系统发布了那些服务,而不需要知道服务具体存在于什么位置,这就
是透明化路由。
- 服务提供者启动
- 服务提供者将相关服务信息主动注册到注册中心
- 服务消费者获取服务注册信息:
poll模式:服务消费者可以主动拉取可用的服务提供者清单
push模式:服务消费者订阅服务(当服务提供者有变化时,注册中心也会主动推送更新后的服务清单给消费者 - 服务消费者直接调用服务提供者
另外,注册中心也需要完成服务提供者的健康监控,当发现服务提供者失效时需要及时剔除。
主流服务中心对比
- Zookeeper
Zookeeper它是一个分布式服务框架,是Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。
简单来说zookeeper本质=存储+监听通知。
znode
Zookeeper用来做服务注册中心,主要是因为它具有节点变更通知功能,只要客户端监听相关服务节点,服务节点的所有变更,都能及时的通知到监听客户端,调用方只要使用Zookeeper的客户端就能实现服务节点的订阅和变更通知功能了,非常方便。另外,Zookeeper的可用性也可以,因为只要半数以上(最少3台)的选举节点存活,整个集群就是可用的。 - Eureka
由Netflix开源,并被Pivatal集成到SpringCloud体系中,它是基于Restful API⻛格开发的服务注册与发现组件。
Eureka有一个自我保护机制,在服务提供者没有发送心跳时,不剔除中断的服务,保证高可用。 - Consul
Consul是由HashiCorp基于Go语言开发的支持多数据中心分布式高可用的服务发布和注册服务软件,采用Raft算法保证服务的一致性,且支持健康检查。 - Nacos
Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。简单来说Nacos就是注册中心+配置中心的组合,帮助我们解决微服务开发必会涉及到的服务注册与发现,服务配置,服务管理等问题。Nacos是Spring Cloud Alibaba 核心组件之一,负责服务注册、发现和配置。
| 组件名 | 语言 | CAP | 对外暴露接口 |
|---|---|---|---|
| Eureka | Java | AP | HTTP |
| Consul | Go | CP | HTTP/DNS |
| Zookeeper | Java | CP | Zookeeper客户端 |
| Nacos | Java | 支持AP/CP切换 | HTTP |
P:分区容错性(一定的要满足的)
C:数据一致性
A:高可用
CAP不可能同时满足三个,要么是AP,要么是CP
服务注册中心组件 Eureka
- Eureka 基础架构

- Eureka 交互流程及原理
下图是官网描述的一个架构图
Eureka包含两个组件:Eureka Server和Eureka Client,Eureka Client是一个Java客户端,用于简化与Eureka Server的交互;Eureka Server提供服务发现的能力,各个微服务启动时,会通过Eureka Client向Eureka Server 进行注册自己的信息(例如网络信息),Eureka Server会存储该服务的信息; - 图中us-east-1c、us-east-1d、us-east-1e代表不同的区也就是不同的机房。
- 图中每一个Eureka Server都是一个集群。
- 图中Application Service作为服务提供者向Eureka Server中注册服务,Eureka Server接受到注册事件会在集群和分区中进行数据同步,Application Client作为消费端(服务消费者)可以从Eureka Server中获取到服务注册信息,进行服务调用。
- 微服务启动后,会周期性地向Eureka Server发送心跳(默认周期为30秒)以续约自己的信息。
- Eureka Server在一定时间内没有接收到某个微服务节点的心跳,Eureka Server将会注销该微服务节点(默认90秒)。
- 每个Eureka Server同时也是Eureka Client,多个Eureka Server之间通过复制的方式完成服务注册列表的同步。
- Eureka Client会缓存Eureka Server中的信息。即使所有的Eureka Server节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者。
Eureka通过心跳检测、健康检查和客户端缓存等机制,提高系统的灵活性、可伸缩性和可用性。
Eureka应用及高可用集群
搭建单例Eureka Server服务注册中心
基于Maven构建SpringBoot工程,在SpringBoot工程之上搭建EurekaServer服务(cloud-eureka-server-8761)
父工程parent中引入Spring Cloud依赖
Spring Cloud 是一个综合的项目,下面有很多子项目,比如eureka子项目(版本号 1.x.x)<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>当前工程pom.xml中引入依赖
<dependencies> <!--Eureka server依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> </dependencies>注意:在父工程的pom文件中手动引入jaxb的jar,因为jdk9之后默认没有加载该模块,EurekaServer使用到,所以需要手动导入,否则EurekaServer服务无法启动。
父工程pom.xml
<!--引入Jaxb,开始--> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-core</artifactId> <version>2.2.11</version> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.2.11</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>2.2.11</version> </dependency> <dependency> <groupId>org.glassfish.jaxb</groupId> <artifactId>jaxb-runtime</artifactId> <version>2.2.10-b140310.1920</version> </dependency> <dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> <version>1.1.1</version> </dependency> <!--引入Jaxb,结束-->application.yml
#Eureka server服务端口 server: port: 8761 spring: application: # 应用名称,会在Eureka中作为服务的id标识(serviceId) name: cloud-eureka-server eureka: instance: hostname: localhost # Eureka服务端本身也可以做客户端,用来连接其他服务端 client: # 客户端与Eureka Server交互的地址,如果是集群,也需要写其它Server的地址 service-url: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ # 自己就是服务不需要注册自己 register-with-eureka: false # 自己就是服务不需要从Eureka Server获取服务信息,默认为true,置为false fetch-registry: falseSpringBoot启动类,使用@EnableEurekaServer声明当前项目为Eureka Server服务。
package com.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication // 声明本项目是一个Eureka服务 @EnableEurekaServer public class CloudEurekaServerApplication { public static void main(String[] args) { SpringApplication.run(CloudEurekaServerApplication.class,args); } }- 执行启动类CloudEurekaServerApplication的main函数
访问http://127.0.0.1:8761,如果看到如下⻚面(Eureka注册中心后台),则表明EurekaServer发布成功

以下两图作一下解释:

搭建Eureka Server HA高可用集群
在互联网应用中,服务实例很少有单个的。
即使微服务消费者会缓存服务列表,但是如果EurekaServer只有一个实例,该实例挂掉,正好微服务消费者本地缓存列表中的服务实例也不可用,那么这个时候整个系统都受影响。
在生产环境中,我们会配置Eureka Server集群实现高可用。Eureka Server集群之中的节点通过点对点(P2P)通信的方式共享服务注册表。我们开启两台 Eureka Server以搭建集群。
修改本机host属性
由于是在个人计算机中进行测试很难模拟多主机的情况,Eureka配置server集群时需要执行host地址。所以需要修改个人电脑中host地址。127.0.0.1 CloudEurekaServerA 127.0.0.1 CloudEurekaServerB复制一个cloud-eureka-server工程(注意修改工程,不要重复),修改两个cloud-eureka-server工程中的yml配置文件。
#原工程的配置文件 spring: application: name: cloud-eureka-server server: port: 8761 eureka: instance: # 在上一步hosts文件中修改过的host地址 hostname: CloudEurekaServerA client: register-with-eureka: true fetch-registry: true serviceUrl: # 如果有其他实例,逗号拼接地址即可 defaultZone: http://CloudEurekaServerB:8762/eureka#复制工程的配置文件 spring: application: name: cloud-eureka-server server: port: 8762 eureka: instance: hostname: CloudEurekaServerB client: register-with-eureka: true fetch-registry: true serviceUrl: defaultZone: http://CloudEurekaServerA:8761/eureka由于集群环境,每个Eureka服务端相对于集群中的其他Eureka服务端来说都是一个客户端,所以配置文件中要注意:
- 在一个实例中,把另外的实例作为了集群中的镜像节点,那么这个http://CloudEurekaServerB:8762/eureka URL中的CloudEurekaServerB就要和其它个profile中的eureka.instance.hostname保持一致。
- register-with-eureka和fetch-registry在单节点时设置为了false, 因为只有一台EurekaServer,并不需要自己注册自己,而现在有了集群,可以在集群的其他节点中注册本服务。
- 启动两个SpringBoot项目(EurekaServer)。
访问两个EurekaServer的管理台⻚面 http://cloudeurekaservera:8761/ 和 http://cloudeurekaserverb:8762/ 会发现注册中心CLOUD-EUREKA-SERVER已经有两个节点,并且registered-replicas (相邻集群复制节点)中已经包含对方。
微服务提供者—>注册到Eureka Server集群
注册微服务(服务部署两个实例,分别占用8080、8081端口)
父工程中引入spring-cloud-commons依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-commons</artifactId> </dependency>pom文件引入坐标,添加eureka client的相关坐标
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>配置application.yml文件
在application.yml中添加Eureka Server高可用集群的地址及相关配置eureka: client: serviceUrl: # eureka server的路径 # 把eureka集群中的所有url都填写了进来,也可以只写一台,因为各个eureka server可以同步注册表 defaultZone: http://cloudeurekaservera:8761/eureka/,http://cloudeurekaserverb:8762/eureka/ instance: # 使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip) prefer-ip-address: true # 自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@- 启动类添加注解@EnableDiscovertClient或@EnableEurekaClient
注意:
- 从Spring Cloud Edgware版本开始,@EnableDiscoveryClient或 @EnableEurekaClient可省略,即不用添加注解也能正常运行。只需加上相关依赖,并进行相应配置,即可将微服务注册到服务发现组件上。
- @EnableDiscoveryClient和@EnableEurekaClient二者的功能是一样的。但是如果选用的是eureka服务器,那么就推荐@EnableEurekaClient,如果是其他的注册中心,那么推荐使用@EnableDiscoveryClient,考虑到通用性就使用@EnableDiscoveryClient
启动类执行,在Eureka Server后台界面可以看到注册的服务实例
微服务消费者—>注册到Eureka Server集群
pom文件引入坐标,添加eureka client的相关坐标
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-commons</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>配置application.yml文件
spring: application: name: service-consumer server: port: 8090 eureka: client: # eureka server的路径 serviceUrl: # 把eureka集群中的所有url都填写了进来,也可以只写一台,因为各个eureka server可以同步注册表 defaultZone: http://lagoucloudeurekaservera:8761/eureka/,http://lagoucloudeurekaserverb:8762/eureka/ instance: # 使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip) prefer-ip-address: true # 自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@在启动类添加注解@EnableDiscoveryClient,开启服务发现
服务消费者调用服务提供者(通过Eureka)
在上一篇RestTemplate的使用中,我们曾用RestTemplate来调用服务端提供的远程方法:
Integer forObject = restTemplate.getForObject("http://localhost:8080/shop/order/" + orderId,Integer.class);通过Eureka对象的方法拼接url改造如下:
在controller中:@Autowired private DiscoveryClient discoveryClient;在对应的handler方法中:
// 1、通过Eureka客户端对象获取Eureka中注册的cloud-eureka-server实例列表,参数对应提供者application.yml文件中的spring.application.name的值 List<ServiceInstance> instanceList = discoveryClient.getInstances("cloud-eureka-server"); // 2、如果有多个实例,选一个(此处是负载均衡的过程,暂不考虑,下一篇会写到,先拿第一个) ServiceInstance serviceInstance = instanceList.get(0); // 3、根据实例的元数据信息获取host、port来拼接请求地址 String host = serviceInstance.getHost(); String port = serviceInstance.getPort(); String url = "http://" + host + ":" + port + "/shop/order/" + orderId; // 4、消费者直接调用提供者 Integer forObject = restTemplate.getForObject(url, Integer.class);Eureka细节详解
Eureka元数据详解
Eureka的元数据有两种:标准元数据和自定义元数据。
标准元数据: 主机名、IP地址、端口号等信息,这些信息都会被发布在服务注册表中,用于服务之间的调用。
自定义元数据: 可以使用eureka.instance.metadata-map配置,符合KEY/VALUE的存储格式。这些元数据可以在远程客户端中访问。eureka: instance: prefer-ip-address: true # 自定义元数据配置 metadata-map: # 自定义元数据(kv自定义) cluster: cl1 region: rn1我们可以在程序中可以使用DiscoveryClient 获取指定微服务的所有元数据信息(SpringBoot测试类举例):
import com.cloud.CloudEurekaServerApplication; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.util.List; import java.util.Map; @SpringBootTest(classes = {CloudEurekaServerApplication.class}) @RunWith(SpringJUnit4ClassRunner.class) public class CloudEurekaServerApplicationTest { @Autowired private DiscoveryClient discoveryClient; @Test public void test() { // 从EurekaServer获取指定微服务实例 List<ServiceInstance> serviceInstanceList = discoveryClient.getInstances("cloud-eureka-server"); // 循环打印每个微服务实例的元数据信息 for (int i = 0; i < serviceInstanceList.size(); i++) { ServiceInstance serviceInstance = serviceInstanceList.get(i); System.out.println(serviceInstance); } } }可以使用Debug方式运行,在System.out.println(serviceInstance)打断点查看元数据:

Eureka客户端详解
服务提供者(也是Eureka客户端)要向EurekaServer注册服务,并完成服务续约等工作。
服务注册详解(服务提供者)
- 当我们导入了eureka-client依赖坐标,配置Eureka服务注册中心地址。
- 服务在启动时会向注册中心发起注册请求,携带服务元数据信息(这就是为什么可以不加@EnableDiscoveryClient或@EnableEurekaClient注解的原因)。
- Eureka注册中心会把服务的信息保存在Map中。
服务续约详解(服务提供者)
服务每隔30秒会向注册中心续约(心跳)一次(分布式理论也称为报活),如果没有续约,租约在90秒后到期,然后服务会被失效。每隔30秒的续约操作我们称之为心跳检测。
# 向Eureka服务中心集群注册服务
eureka:
instance:
# 租约续约间隔时间,默认30秒
lease-renewal-interval-in-seconds: 30
# 租约到期,服务时效时间,默认值90秒,服务超过90秒没有发生心跳,EurekaServer会将服务从列表移除
lease-expiration-duration-in-seconds: 90往往不需要我调整这两个配置。
获取服务列表详解(服务消费者)
每隔30秒服务会从注册中心中拉取一份服务列表,这个时间可以通过配置修改。往往不需要调整。
# 向Eureka服务中心集群注册服务
eureka:
client:
# 每隔多久拉取一次服务列表
registry-fetch-interval-seconds: 30- 服务消费者启动时,从 EurekaServer服务列表获取只读备份,缓存到本地。
- 每隔30秒,会重新获取并更新数据。
每隔30秒的时间可以通过配置eureka.client.registry-fetch-interval-seconds修改。
Eureka服务端详解
服务下线
- 当服务正常关闭操作时,会发送服务下线的REST请求给EurekaServer。
- 服务中心接受到请求后,将该服务置为下线状态。
失效剔除
Eureka Server会定时(此间隔值由服务端设置的eureka.server.eviction-interval-timer-in-ms,默认60s)进行检查,如果发现实例在在一定时间(此值由客户端设置的eureka.instance.lease-expiration-duration-in-seconds定义,默认值为90s)内没有收到心跳,则会注销此实例。
自我保护
服务提供者会向注册中心定期续约(服务提供者和注册中心通信),如果服务提供者和注册中心之间的网络有问题,那么不代表服务提供者不可用,不代表服务消费者无法访问服务提供者。
如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制。
为什么会有自我保护机制?
默认情况下,如果Eureka Server在一定时间内(默认90秒)没有接收到某个微服务实例的心跳,Eureka Server将会移除该实例。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,而微服务本身是正常运行的,此时不应该移除这个微服务,所以引入了自我保护机制。
服务中心⻚面会显示如下提示信息:
当处于自我保护模式时:
- 不会剔除任何服务实例(可能是服务提供者和EurekaServer之间网络问题),保证了大多数服务依然可用。
- Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可用,当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中。
在Eureka Server工程中通过eureka.server.enable-self-preservation配置可关停自我保护,默认值是打开。
eureka: server: # 关闭自我保护模式(默认为打开) enable-self-preservation: false建议生产环境打开自我保护机制
Eureka核心源码剖析
Eureka Server启动过程
入口:SpringCloud充分利用了SpringBoot的自动装配的特点
- 观察eureka-server的jar包,发现在META-INF下面有配置文件spring.factories

springboot应用启动时会加载EurekaServerAutoConfiguration自动配置类 EurekaServerAutoConfiguration类
首先观察类头分析
第二图中的标注1需要有一个marker bean,才能装配Eureka Server,这个marker 其实是由@EnableEurekaServer注解决定的

也就是说只有添加了@EnableEurekaServer注解,才会有后续的动作,这是成为一个EurekaServer的前提
第二图中的标注2,关注EurekaServerAutoConfiguration


而在com.netflix.eureka.cluster.PeerEurekaNodes#start方法中
回到主配置类EurekaServerAutoConfiguration中

回到主配置类EurekaServerAutoConfiguration中

第二图中的标注3,关注EurekaServerInitializerConfiguration

重点关注,进入org.springframework.cloud.netflix.eureka.server.EurekaServerBootstrap#contextInitialized
重点关注initEurekaServerContext()
研究一下上图中的syncUp方法
继续研究com.netflix.eureka.registry.AbstractInstanceRegistry#register(提供实例注册功能)
继续研究com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#openForTraffic
进入postInit()方法查看
至此,Eureka Server启动过程分析完成。Eureka Server服务接口暴露策略
在Eureka Server启动过程中主配置类EurekaServerAutoConfiguration中注册了Jersey框架(是一个发布restful⻛格接口的框架,类似于SpringMVC)

注入的Jersey细节
扫描classpath下的packages已经定义好了
对外提供的接口服务,在Jersey中叫做资源Resource
这些就是使用Jersey发布的供Eureka Client调用的Restful⻛格服务接口(完成服务注册、心跳续约等接口)。Eureka Server服务注册接口(接受客户端注册服务)
ApplicationResource类的addInstance()方法中代码:registry.register(info,"true".equals(isReplication));

com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#register - 注册服务信息并同步到其它Eureka节点
AbstractInstanceRegistry#register():注册,实例信息存储到注册表(是一个ConcurrentHashMap)- Registers a new instance with a given duration.
* @see com.netflix.eureka.lease.LeaseManager#register(java.lang.Object, int,boolean)
*/
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
try {
read.lock(); //读锁
// registry是保存所有应用实例信息的Map:ConcurrentHashMap<String,Map<String, Lease>>
// 从registry中获取当前appName的所有实例信息
Map<String, Lease> gMap = registry.get(registrant.getAppName());
REGISTER.increment(isReplication); //注册统计+1
// 如果当前appName实例信息为空,新建Map
if (gMap == null) {
final ConcurrentHashMap<String, Lease> gNewMap = new ConcurrentHashMap<String, Lease >();
gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
if (gMap == null) {gMap = gNewMap;}
}
// 获取实例的Lease租约信息
LeaseexistingLease = gMap.get(registrant.getId());
// Retain the last dirty timestamp without overwriting it, if there is already a lease
// 如果已经有租约,则保留最后一个脏时间戳而不覆盖它
// 比较当前请求实例租约 和 已有租约的LastDirtyTimestamp,选择靠后的
if (existingLease != null && (existingLease.getHolder() != null)) {
Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {logger.warn("There is an existing lease and the existing lease's dirty timestamp {} is greater" + " than the one that is being registered {}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp); logger.warn("Using the existing instanceInfo instead of the new instanceInfo as the registrant"); registrant = existingLease.getHolder();}
}
else {
// The lease does not exist and hence it is a new registration
// 如果之前不存在实例的租约,说明是新实例注册
// expectedNumberOfRenewsPerMin期待的每分钟续约数+2(因为30s一个)
// 并更新numberOfRenewsPerMinThreshold每分钟续约阀值(85%)
synchronized (lock) {if (this.expectedNumberOfRenewsPerMin > 0) { // Since the client wants to cancel it, reduce the threshold // (1 for 30 seconds, 2 for a minute) this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin + 2; this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold()); }}
logger.debug("No previous lease information found; it is new registration");
}
Leaselease = new Lease (registrant, leaseDuration);
if (existingLease != null) {
lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
}
//当前实例信息放到维护注册信息的Map
gMap.put(registrant.getId(), lease);
// 同步维护最近注册队列
synchronized (recentRegisteredQueue) {
recentRegisteredQueue.add(new Pair<Long, String>(System.currentTimeMillis(), registrant.getAppName() + "(" + registrant.getId() +")"));
}
// This is where the initial state transfer of overridden status happens
// 如果当前实例已经维护了OverriddenStatus,将其也放到此Eureka Server的overriddenInstanceStatusMap中
if(!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())){
logger.debug("Found overridden status {} for instance {}. Checking to see if needs to be add to the "+ "overrides", registrant.getOverriddenStatus(), registrant.getId());
if (!overriddenInstanceStatusMap.containsKey(registrant.getId())){logger.info("Not found overridden id {} and hence adding it", registrant.getId()); overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());}
}
InstanceStatus overriddenStatusFromMap = overriddenInstanceStatusMap.get(registrant.getId());
if (overriddenStatusFromMap != null) {
logger.info("Storing overridden status {} from map", overriddenStatusFromMap);
registrant.setOverriddenStatus(overriddenStatusFromMap);
}
// Set the status based on the overridden status rules
// 根据overridden status规则,设置状态
InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus(registrant, existingLease, isReplication);
registrant.setStatusWithoutDirty(overriddenInstanceStatus);
// If the lease is registered with UP status, set lease service up timestamp
// 如果租约以UP状态注册,设置租赁服务时间戳
if (InstanceStatus.UP.equals(registrant.getStatus())) {
lease.serviceUp();
}
registrant.setActionType(ActionType.ADDED); //ActionType为 ADD
recentlyChangedQueue.add(new RecentlyChangedItem(lease)); //维护recentlyChangedQueue
registrant.setLastUpdatedTimestamp(); //更新最后更新时间
// 使当前应用的ResponseCache失效
invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
logger.info("Registered instance {}/{} with status { (replication={})", registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication);
} finally {
read.unlock(); //读锁
}
}PeerAwareInstanceRegistryImpl#replicateInstanceActionsToPeers  ## Eureka Server服务续约接口(接受客户端续约) InstanceResource的renewLease方法中完成客户端的心跳(续约)处理,关键代码: registry.renew(app.getName(), id, isFromReplicaNode);   com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#renew  replicateInstanceActionsToPeers() 复制Instance实例操作到其它节点private void replicateInstanceActionsToPeers(Action action, String appName, String id, InstanceInfo info, InstanceStatus newStatus, PeerEurekaNode node) {
try {
InstanceInfo infoFromRegistry = null;
CurrentRequestVersion.set(Version.V2);
switch (action) {
case Cancel: //取消node.cancel(appName, id); break;case Heartbeat: //心跳
InstanceStatus overriddenStatus = overriddenInstanceStatusMap.get(id); infoFromRegistry = getInstanceByAppAndId(appName, id, false); node.heartbeat(appName, id, infoFromRegistry, overriddenStatus, false); break;case Register: //注册
node.register(info); break;case StatusUpdate: //状态更新
infoFromRegistry = getInstanceByAppAndId(appName, id, false); node.statusUpdate(appName, id, newStatus, infoFromRegistry); break;case DeleteStatusOverride: //删除OverrideStatus
infoFromRegistry = getInstanceByAppAndId(appName, id, false); node.deleteStatusOverride(appName, id, infoFromRegistry); break;}
} catch (Throwable t) {
logger.error("Cannot replicate information to {} for action {}", node.getServiceUrl(), action.name(), t);
}
}renew()方法中—>leaseToRenew.renew()—>对最后更新时间戳进行更新 
至此,续约完成。
Eureka Client注册服务
启动过程:Eureka客户端在启动时也会装载很多配置类,我们通过spring-cloud-netflix-eureka-client-2.1.0.RELEASE.jar下的spring.factories文件可以看到加载的配置类
引入jar就会被自动装配,分析EurekaClientAutoConfiguration类头
如果不想作为客户端,可以设置eureka.client.enabled=false
回到主配置类EurekaClientAutoConfiguration
EurekaClient启动过程:
- 读取配置文件

- 启动时从EurekaServer获取服务实例信息


观察父类DiscoveryClient()
在另外一个构造器中



- 注册自己到EurekaServer

DiscoveryClient#register
底层使用Jersey客户端进行远程请求。 开启一些定时任务(心跳续约,刷新本地服务缓存列表)

刷新本地缓存

心跳续约定时任务



Eureka Client下架服务

com.netflix.discovery.DiscoveryClient#shutdown

评论已关闭