没有简介
背景
小程序Dog终于从传统的开发框架中跑出来了,进公司的第一个项目也是现在在座的项目使用的是Spring Cloud搭建的为服务架构,目前用到的技术Spring Cloud 全家桶,使用fegin为服务间通信,(这后面的基本没见过)ribbon负载均衡器,Hystrix断路器;数据库使用mysql,多服务同步缓存数据使用rabbitmq,这些技术也准备接下来作为笔记记录,
笔记一之Spring Cloud Eureka
基本的部署没什么可讲的,下面带着疑问寻找答案
问题一:Eureka是如何实现服务注册与发现的?
用了一些时间看了下大神的分析帖子,【SpringCloud Eureka源码】从Eureka Client发起注册请求到Eureka Server处理的整个服务注册过程(上)https://www.cnblogs.com/trust-freedom/p/10218145.html,大致明白了一些,所以下面就这边文章记录下笔记,可能还有自己的一些理解。
首先是从客户端注解@EnableDiscoveryClient开始,源码里面只有一个方法,看名称就是自动注册的意思,默认是true;同时通过import导入一部分配置,主要也是对自动注册的一些配置,配置类EurekaClientAutoConfiguration中向Spring容器注入了EurekaDiscoveryClientConfiguration.marker.class,也是为了让该配置可以启用
EurekaDiscoveryClientConfiguration
-
利用eurekaDdiscoverClientMarker向Spring容器中注入内部类EurekaDiscoveryClientConfiguration.Marker.class
-
配置RefreshScopeRefreshedEvent。创建监听RefreshScopeRefreshedEvent事件的监听器满足在使用RefreshScope刷新时可以重建EurekaClient(没看)。
-
配置EurekaHealthCheckHandler健康检查
所以它主要是向Spring容器中注入内部类,用于让自动配置满足启用条件
EurekaClientAuroConfiguration
-
注册了spring cloud包下的EurekaClientConfigBean,这是个对netflix的EurekaClientConfig客户端配置接口的实现
-
注册了spring cloud包下的EurekaInstanceConfigBean,这是个对netflix的EurekaInstanceConfig实例信息配置接口的实现
-
其他还有一些配置这里不细究,如注册了一些AutoServiceRegistration,即客户端自动注册的组件,注册EurekaHealthIndicator,为/health端点提供Eureka相关信息
-
这里主要讲一下EurekaClient: netflix的接口类,用于和Eureka Server交互的客户端,而netflix的默认实现是DiscoveryClient
首先看到两个配置EurekaClientConfguration和RefreshableEurekaClientConfiguration 通过判断是否满足RefreshScope,来区分注入的Bean是否带有 @Lazy + @RefreshScope 配置中注册了两个类,一个是CloudEurekaClient,另一个是ApplicationInfoManager
所以总结一下当前类,就是添加一些组件的配置,同时当满足一些条件后向Spring容器中注册CloudEurekaClient,继承自DiscoveryClient并实现了EurekaClient接口
DiscoveryClient
DiscoveryClient 问题一重点分析的源码
首先介绍下当前类,它是EurekaClient的默认实现,刚刚的CloudEurekaClient虽然继承于他,但根据类注释,主要是为了重新onCacheRefreshed方法。
Subclass of DiscoveryClient that send a HeartbeatEvent when
CloudRurekaClient oncacheRefreshed is called
public class CludEurekaClient ...
onCacheRefreshed这个方法主要是从Eureka Server fetchRegistry()获取服务列表之后用于以广播方式通知缓存刷新事件的,其实DiscoveryClient也有onCacheRefreshed()方法的实现,但由于DiscoveryClient是Netflix的类,只发送了com.netflix.discovery.EurekaEvent,而CloudEurekaClient使用Spring的ApplicationEventPublisher,发送了HeartbeatEvent
---------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------
DiscoveryClient类的构造方法里创建了各种Executor 以及 eurekaTransport、instanceRegionChecker,
然后利用initScheduledTasks()对这些定时任务初始化。
这一段代码注释写得太好了,所以直接copy过来了
/**
* Initializes all scheduled tasks.
*/
private void initScheduledTasks() {
// 1、如果要从Eureka Server获取服务列表
if (clientConfig.shouldFetchRegistry()) {
// registry cache refresh timer
// 从eureka服务器获取注册表信息的频率(默认30s)
// 同时也是单次获取服务列表的超时时间
int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
// 如果缓存刷新超时,下一次执行的delay最大是registryFetchIntervalSeconds的几倍(默认10),默认每次执行是上一次的2倍
int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
/**
* 【#### 执行CacheRefreshThread,服务列表缓存刷新任务 ####】
* 执行TimedSupervisorTask监督任务的定时器,具体执行器为cacheRefreshExecutor,任务为CacheRefreshThread
*/
scheduler.schedule(
new TimedSupervisorTask(
"cacheRefresh", //监控名
scheduler,
cacheRefreshExecutor,
registryFetchIntervalSeconds, //指定具体任务的超时时间
TimeUnit.SECONDS,
expBackOffBound,
new CacheRefreshThread()
),
registryFetchIntervalSeconds, TimeUnit.SECONDS);
}
// 2、如果要注册到Eureka Server
if (clientConfig.shouldRegisterWithEureka()) {
// 续租的时间间隔(默认30s)
int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
// 如果心跳任务超时,下一次执行的delay最大是renewalIntervalInSecs的几倍(默认10),默认每次执行是上一次的2倍
int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
logger.info("Starting heartbeat executor: " + "renew interval is: " + renewalIntervalInSecs);
// Heartbeat timer
/**
* 【#### 执行HeartbeatThread,发送心跳数据 ####】
* 执行TimedSupervisorTask监督任务的定时器,具体执行器为heartbeatExecutor,任务为HeartbeatThread
*/
scheduler.schedule(
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
),
renewalIntervalInSecs, TimeUnit.SECONDS);
// InstanceInfo replicator
/**
* 【#### InstanceInfo复制器 ####】
* 启动后台定时任务scheduler,线程名为 DiscoveryClient-InstanceInfoReplicator-%d
* 默认每30s执行一次定时任务,查看Instance信息(DataCenterInfo、LeaseInfo、InstanceStatus)是否有变化
* 如果有变化,执行 discoveryClient.register()
*/
instanceInfoReplicator = new InstanceInfoReplicator(
this, //当前DiscoveryClient
instanceInfo, //当前实例信息
clientConfig.getInstanceInfoReplicationIntervalSeconds(),//InstanceInfo的复制间隔(默认30s)
2); // burstSize
/**
* 【StatusChangeListener 状态改变监听器】
*/
statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
@Override
public String getId() {
return "statusChangeListener";
}
@Override
public void notify(StatusChangeEvent statusChangeEvent) {
if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||
InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {
// log at warn level if DOWN was involved
logger.warn("Saw local status change event {}", statusChangeEvent);
} else {
logger.info("Saw local status change event {}", statusChangeEvent);
}
//使用InstanceInfo复制器 scheduler.submit()一个Runnable任务
//后台马上执行 discoveryClient.register()
instanceInfoReplicator.onDemandUpdate();
}
};
/**
* 是否关注Instance状态变化,使用后台线程将状态同步到eureka server(默认true)
* 调用 ApplicationInfoManager#setInstanceStatus(status) 会触发
* 将 StatusChangeListener 注册到 ApplicationInfoManager
*/
if (clientConfig.shouldOnDemandUpdateStatusChange()) {
applicationInfoManager.registerStatusChangeListener(statusChangeListener);
}
// 启动InstanceInfo复制器
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
}
// 当前服务实例不注册到Eureka Server
else {
logger.info("Not registering with Eureka server per configuration");
}
}
总结来说,他主要做了下面几件事
- 如果shouldFetchRegistry=true就从server段拉取服务列表同时刷新列表缓存,默认每30秒获取一次
- 如果作为客户端注册到server上,默认每30秒发送一次心跳用以维持连接,通过refreshInstanceInfo()刷新状态,刷新后如果发现有脏数据,即实例发生了变更,还未同步给Server的数据,就发起注册若有变化,则执行discoveryClient.register()
这里有个小插曲,instanceInfoReplicator复制器在启动后首先设置为脏数据,然后启动定时任务每40s跑一次run()方法发起注册,同时我们在前面看到,当Spring初始化自动注册配置EurekaClientAutoConfiguration,会注册相关组件EurekaRegistration、EurekaServiceRegistry、EurekaAutoServiceRegistration,同时会在Spring容器refresh要完毕时触发生命周期方法start(),调用EurekaServiceRegistry的register方法,注册EurekaRegistration这个本地实例信息,伺候通过健康检查registerHealthCheck方法调用instanceInfoReplicator中的onDemandUpdate按需更新,onDemandUpdate方法通过cancel掉自动更新任务后直接执行本类run()方法调用服务注册的真实方法discoveryClient.register()。
用大牛的分析总结下这段话就是
所以,以我判断,Eureka Client启动时的自动注册大多数应该是Spring Cloud的服务自动注册机制,在Spring容器基本启动完毕时,触发服务自动注册操作,其中会使用ApplicationInfoManager更新实例状态为初始状态UP,一旦实例状态变更会被马上监听到,执行复制器的InstanceInfoReplicator.onDemandUpdate()按需更新,马上执行一次discoveryClient.register()操作
至此基本的注册准备就讲完了下面一篇文章主要讲解注册是如何实现的
真正的注册方法:DiscoveryClient#register() 注册
链接:DiscoveryClient#register() 注册
问题二:项目中用到的客户端注册使用的是@EnableEurekaClient注解,他和EnableDiscoveryClient有什么不同?
待续…