服务注册是Spring Cloud的关键功能,本文着重来分析eureka-client服务注册的过程。
服务注册
要想将一个服务注册到Eureka Server非常简单:
在pom.xml文件中加入Eureka Server依赖
1
2
3
4<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>在启动类上添加注解
@EnableDiscoveryClient
或@EnableEurekaClient
1
2
3
4
5
6
7
public class EurekaClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class, args);
}
}配置
1
2
3
4
5
6
7
8
9
10
11
12
13spring:
application:
name: eureka-client
server:
port: 8762
eureka:
instance:
hostname: localhost
client:
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:8761/eureka/
从Spring Cloud Edgware开始,注解@EnableDiscoveryClient
或@EnableEurekaClient
可省略。只需加上相关依赖,并进行相应配置,即可将微服务注册到服务发现组件上。
Eureka-client注册服务的原理
我们知道,在初始化过程中,SpringBoot会扫描spring.factories
,加载其中的配置类。Eureka-client的spring.factories
如下所示:
1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ |
我们重点关注两个类:EurekaDiscoveryClientConfiguration
和EurekaClientAutoConfiguration
。
因为在EurekaClientAutoConfiguration
有如下条件:
1 | (EurekaDiscoveryClientConfiguration.Marker.class) |
因此首先执行EurekaDiscoveryClientConfiguration
配置类,加载其中的配置,然后再去执行EurekaClientAutoConfiguration
。
EurekaClientAutoConfiguration
EurekaClientAutoConfiguration类的主要功能是配置EurekaClient。其中有个关键的内部类RefreshableEurekaClientConfiguration
:
1 |
|
RefreshableEurekaClientConfiguration
的注释@ConditionOnRefreshScope
定义如下:
1 | ({ ElementType.TYPE, ElementType.METHOD }) |
因为在spring-cloud-context
包的spring.factories
中有这样的配置:
1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ |
其中包含了RefreshAutoConfiguration
,所以@ConditionalOnRefreshScope
注释上的@ConditionalOnBean(RefreshAutoConfiguration.class)
是生效的。因此Spring会创建RefreshableEurekaClientConfiguration
,包括其中的EurekaClient
,其实例为CloudEurekaClient
。
CloudEurekaClient
CloudEurekaClient的继承关系如下:
它继承了com.netflix.discovery.DiscoveryClient
。在构造方法中调用了initScheduledTasks()
方法来初始化各种定时任务:
1 | public class DiscoveryClient implements EurekaClient { |
InstanceInfoReplicator服务注册的定时任务
InstanceInfoReplicator
类的功能是更新本地的服务实例信息,并将本地的服务实例信息复制到注册服务中。其定时周期由EurekaClientConfig.getInstanceInfoReplicationIntervalSeconds()
控制,默认为30秒。
DiscoveryClient.initScheduledTasks()
方法中调用InstanceInfoReplicator.start()
方法启动InstanceInfoReplicator
:
1 | public void start(int initialDelayMs) { |
流程如下:
- 调用
compareAndSet
确保只有InstanceInfoReplicator只启动一个定时任务。 - 调用
setIsDirty
将本地的实例信息设置为脏数据,这是为了向注册中心注册服务。 - 然后调用
schedule
启动延时任务
延时任务的执行逻辑在run
方法中:
1 | public void run() { |
流程如下:
- 调用
DiscoveryClient.refreshInstanceInfo
刷新当前本地的实例信息。如果发生了改变,实例信息中的isDirty
标记会被设置为true
- 检查实例信息是否发生了改变,如果发生改变则调用
DiscoveryClient.register
方法将实例信息注册到注册中心 - 再次调用
schedule
启用延时任务,相当于周期性的执行InstanceInfoReplicator任务
刷新实例信息
刷新实例信息的任务在DiscoveryClient.refreshInstanceInfo
方法中,主要流程如下:
- 调用
ApplicationInfoManager.refreshDataCenterInfoIfRequired
检查Server的hostname是否发生了修改 - 调用
ApplicationInfoManager.refreshLeaseInfoIfRequired
检查lease.duration
和lease.renewalInterval
两个续约配置是否发生了修改 - 调用
HealthCheckCallbackToHandlerBridge.getStatus
获取服务实例的状态,并设置服务实例的状态
注册服务实例信息
注册服务实例信息的任务在DiscoveryClient.register
方法中,其最终调用的方法是AbstractJerseyEurekaHttpClient.register(InstanceInfo info)
。该方法向Eureka Server发送POST请求,请求的urlPath是apps/EUREKA-CLIENT
,EUREKA-CLIENT
是服务实例的名称,在本例中完整的请求地址是http://localhost:8761/eureka/apps/EUREKA-CLIENT
。将服务实例信息InstanceInfo通过POST请求发送给Eureka Server完成注册。
刷新服务列表缓存的定时任务
刷新服务列表缓存的定时任务由以下代码启动:
1 | scheduler.schedule( |
其定时周期由EurekaClientConfig.getRegistryFetchIntervalSeconds()
控制,默认为30秒。
在TimedSupervisorTask
中也是通过循环调用schedule
的方式形成一个周期任务,以定时执行CacheRefreshThread
线程。
CacheRefreshThread
的任务就是调用refreshRegistry
方法:
1 | class CacheRefreshThread implements Runnable { |
refreshRegistry
refreshRegistry
的任务是获取注册的所有服务,主要的流程如下:
- 调用
EurekaClientConfig.fetchRegistryForRemoteRegions
重新获取注册中心的地址,因为这些配置有可能发生动态修改。 - 调用
DiscoveryClient.fetchRegistry
获取注册信息
fetchRegistry
fetchRegistry
的任务是获取注册信息,主要流程如下:
- 调用
DiscoveryClient.getApplications
方法获取本地保存的所有注册信息 - 判断是否需要获取全量注册信息,分别调用
DiscoveryClient.getAndStoreFullRegistry
和DiscoveryClient.getAndUpdateDelta
方法 - 对注册信息设置HashCode
- 调用
onCacheRefreshed
广播CacheRefreshedEvent
事件通知服务发生改变。例如Ribbon收到事件后会更新它保存的服务信息 - 调用
updateInstanceRemoteStatus
更新实例的状态
getAndStoreFullRegistry
DiscoveryClient.getAndStoreFullRegistry
的任务是从Eureka Server中获取所有的注册信息,然后保存在本地。主要流程如下:
调用
EurekaHttpClientDecorator.getApplications
方法,返回EurekaHttpResponse最终调用的是
AbstractJerseyEurekaHttpClient.getApplicationsInternal
,该方法向Eureka Server发送请求,请求的urlPath是apps/
,在本例中完整的请求地址是http://localhost:8761/eureka/apps/
。根据返回的数据生成Applications
实例。如果返回的
Applications
不为null,将其保存到DiscoveryClient.localRegionApps
变量中
getAndUpdateDelta
DiscoveryClient.getAndUpdateDelta
的任务是从Eureka Server中获取增量注册信息,更新本地保存的信息。主要流程如下:
调用
EurekaHttpClientDecorator.getDelta
方法,最终调用的是
AbstractJerseyEurekaHttpClient.getApplicationsInternal
,该方法向Eureka Server发送请求,请求的urlPath是apps/delta
,在本例中完整的请求地址是http://localhost:8761/eureka/apps/delta
。根据返回的数据生成Applications
实例。如果返回的delta为null,则调用
getAndStoreFullRegistry
获取全量的数据调用
Discovery.updateDelta
更新增量服务信息遍历增量服务信息,根据服务信息的操作类型(新增、修改、删除)进行相应的处理
服务续约的定时任务
服务续约的定时任务由以下代码启动:
1 | scheduler.schedule( |
在TimedSupervisorTask
中也是通过循环调用schedule
的方式形成一个周期任务,以定时执行HeartbeatThread
线程。其定时周期由LeaseInfo.renewalIntervalInSecs
变量控制,默认为DEFAULT_LEASE_RENEWAL_INTERVAL
,即30秒。
HeartbeatThread
的任务就是调用renew
方法:
1 | private class HeartbeatThread implements Runnable { |
renew
DiscoveryClient.renew
方法的任务是向Eureka Server发送服务续约请求,流程如下:
调用
EurekaHttpClientDecorator.sendHeartBeat
发送心跳请求最终调用的是
AbstractJerseyEurekaHttpClient.sendHeartBeat
,该方法向Eureka Server发送心跳请求,请求的urlPath是apps/EUREKA-CLIENT/wangqideimac.lan:eureka-client:8762
,在本例中完整的请求地址是http://localhost:8761/eureka/apps/EUREKA-CLIENT/wangqideimac.lan:eureka-client:8762?status=UP&lastDirtyTimestamp=1530096210220
。其中EUREEKA-CLIENT
表示服务名称,wangqideimac.lan:eureka-client:8762
表示服务id,status
表示服务的状态,lastDirtyTimestamp
表示实例更新的时间。如果返回的状态是404,表示当前服务还没有注册过,于是调用
register
方法向Eureka Server注册服务。
服务下线
服务下线一般在服务关闭(shut down)的时候调用,用来把自身的服务从Eureka Server中删除,以防客户端调用不存在的服务。
服务下线的任务在Discovery.unregister
方法中,其最终调用的方法是AbstractJerseyEurekaHttpClient.cancel(String appName, String id)
。该方法向Eureka Server发送DELETE请求,请求的urlPath是apps/EUREKA-CLIENT/wangqideimac.lan:eureka-client:8762
,其中EUREEKA-CLIENT
表示服务名称,wangqideimac.lan:eureka-client:8762
表示服务id。在本例中完整的请求地址是http://localhost:8761/eureka/apps/EUREKA-CLIENT/wangqideimac.lan:eureka-client:8762
。
总结
经过前文的总结,我们可以看到,服务注册的过程是围绕CloudEurekaClient
来进行的,它的父类DiscoveryClient
在初始化的过程中会调用initScheduledTasks
方法,其中会创建三个定时任务:
服务注册。
其定时周期由
EurekaClientConfig.getInstanceInfoReplicationIntervalSeconds()
控制,默认为30秒。调用
DiscoveryClient.register
方法。向Eureka Server发送请求,请求的urlPath是”apps/{app_name}”,app_name是服务实例的名称。将服务实例信息InstanceInfo通过POST请求发送给Eureka Server完成注册。本示例完整的请求实例:”http://localhost:8761/eureka/apps/EUREKA-CLIENT/"
服务续约。
其定时周期由
LeaseInfo.renewalIntervalInSecs
变量控制,默认为DEFAULT_LEASE_RENEWAL_INTERVAL
,即30秒。调用
DiscoveryClient.renew
方法。向Eureka Server发送请求,请求的urlPath是”apps/{app_name}/{id}:8762?status={status}&lastDirtyTimestamp={timestamp}”,其中app_name表示服务名称,id表示服务id,status表示服务的状态,timestamp表示实例更新的时间。刷新服务列表缓存。调用
DiscoveryClient.fetchRegistry
方法,其定时周期由
EurekaClientConfig.getRegistryFetchIntervalSeconds()
控制,默认为30秒。向Eureka Server发送请求,全量请求的urlPath是”apps/“,增量请求的urlPath是”apps/delta”
http://blog.didispace.com/spring-cloud-eureka-register-detail/
https://my.oschina.net/u/3039671/blog/1546168
http://www.itmuch.com/spring-cloud/edgware-new-optional-enable-discovery-client/
https://blog.csdn.net/Mr_rain/article/details/78790292