Github地址 https://github.com/liushangzaibeijing/eureka
当Eureka客户端向Eureka Server注册时,它提供自身的元数据,比如IP地址、端口,运行状况指示符URL,主页等。
Eureka客户会每隔30秒发送一次心跳来续约。 通过续约来告知Eureka Server该Eureka客户仍然存在,没有出现问题。 正常情况下,如果Eureka Server在90秒没有收到Eureka客户的续约,它会将实例从其注册表中删除。 建议不要更改续约间隔。
Eureka客户端从服务器获取注册表信息,并将其缓存在本地。客户端会使用该信息查找其他服务,从而进行远程调用。该注册列表信息定期(每30秒钟)更新一次。每次返回注册列表信息可能与Eureka客户端的缓存信息不同, Eureka客户端自动处理。如果由于某种原因导致注册列表信息不能及时匹配,Eureka客户端则会重新获取整个注册表信息。 Eureka服务器缓存注册列表信息,整个注册表以及每个应用程序的信息进行了压缩,压缩内容和没有压缩的内容完全相同。Eureka客户端和Eureka 服务器可以使用JSON / XML格式进行通讯。在默认的情况下Eureka客户端使用压缩JSON格式来获取注册列表的信息。
Eureka客户端在程序关闭时向Eureka服务器发送取消请求。 发送请求后,该客户端实例信息将从服务器的实例注册表中删除。该下线请求不会自动完成,它需要调用以下内容:
DiscoveryManager.getInstance().shutdownComponent();
在默认的情况下,当Eureka客户端连续90秒没有向Eureka服务器发送服务续约,即心跳,Eureka服务器会将该服务实例从服务注册列表删除,即服务剔除。
从例子中我们可以看到对应的eureka 客户端添加相关的eureka依赖后只需要在主应用的启动类上添加@EnableEurekaClient 或者 @EnableDiscoveryClient即可
@EnableDiscoveryClient和@EnableEurekaClient共同点就是:都是能够让注册中心能够发现,扫描到该服务。
不同点:@EnableEurekaClient只适用于Eureka作为注册中心,@EnableDiscoveryClient 可以是其他注册中心。
使用该注解会在服务启动的过程中初始化DiscoverClient类。同时也是我们分析Eureka组件的入口类,下面让我们来具体分析一个一个微服务注册的过程。
DiscoveryClient类。相关的UML类图如下:
在spring服务启动过程中DiscoverClient类的initScheduledTasks()方法会被调用
该方法主要做了
/**
* eureka 客户端初始化操作 主要是启动如下的定时任务
* 1、服务获取定时任务 主要实现服务拉取(cacheRefresh 本地服务列表信息的缓存刷新)
* 2、服务续约(心跳检测)定时任务 每隔指定的时间向eureka server 发送心跳检测
* 3、服务注册/刷新
*/
private void initScheduledTasks() {
int renewalIntervalInSecs;
int expBackOffBound;
/**
* clientConfig 为我们对应的在application,yml中配置的eureka的配置
* 如果没有配置使用eureka的默认配置
*/
//从eureka.client.fetch-registry参数设置进来,默认为ture
if (this.clientConfig.shouldFetchRegistry()) {
//服务续约的间隔时间 默认30秒
renewalIntervalInSecs = this.clientConfig
.getRegistryFetchIntervalSeconds();
//10 最大延迟时间的倍数 与服务续约时间相乘的结果作为最大的延迟时间
expBackOffBound = this.clientConfig.
getCacheRefreshExecutorExponentialBackOffBound();
//执行 拉取服务列表的定时任务
this.scheduler.schedule(
new TimedSupervisorTask(
"cacheRefresh",
this.scheduler,
this.cacheRefreshExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new DiscoveryClient.CacheRefreshThread()),
(long)renewalIntervalInSecs,
TimeUnit.SECONDS);
}
//从eureka.client.register-with-eureka参数设置进来,默认为ture 是否进行注册
//如果不需要注册,则该服务并不需要进行服务续约
if (this.clientConfig.shouldRegisterWithEureka()) {
//服务续约时间间隔也为30秒
renewalIntervalInSecs = this.instanceInfo.
getLeaseInfo().getRenewalIntervalInSecs();
//10 最大延迟时间的倍数 与服务续约时间相乘的结果作为最大的延迟时间
expBackOffBound = this.clientConfig
.getHeartbeatExecutorExponentialBackOffBound();
logger.info("Starting heartbeat executor:
renew interval is: " + renewalIntervalInSecs);
//服务续约的定时任务
this.scheduler.schedule(
new TimedSupervisorTask("heartbeat",
this.scheduler,
this.heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new DiscoveryClient.HeartbeatThread(null)),
(long)renewalIntervalInSecs,
TimeUnit.SECONDS);
//创建包含微服务实例的线程任务 InstanceInfoReplicator
//该类实现了Runable接口,会定时被调用
//实现服务的注册和eureka server 服务实例信息的刷新
this.instanceInfoReplicator = new InstanceInfoReplicator(this,
this.instanceInfo,
this.clientConfig.
getInstanceInfoReplicationIntervalSeconds(),
2);
//创建服务状态改变的监听器 对于服务状态的改变
//也需要使用该InstanceInfoReplicator对象进行服务的重新注册/刷新
this.statusChangeListener = new ApplicationInfoManager
.StatusChangeListener() {
public String getId() {
return "statusChangeListener";
}
public void notify(StatusChangeEvent statusChangeEvent) {
if (InstanceInfo.InstanceStatus.DOWN
!= statusChangeEvent.getStatus() &&
InstanceInfo.InstanceStatus.DOWN !=
statusChangeEvent.getPreviousStatus()) {
DiscoveryClient.logger.info(
"Saw local status change event {}", statusChangeEvent);
} else {
DiscoveryClient.logger.warn("Saw local status change event {}",
statusChangeEvent);
}
//最终也是调用InstanceInfoReplicator的run()方法
DiscoveryClient.this.instanceInfoReplicator.onDemandUpdate();
}
};
//添加时间监听
if (this.clientConfig.shouldOnDemandUpdateStatusChange()) {
this.applicationInfoManager
.registerStatusChangeListener(this.statusChangeListener);
}
//启动服务注册和刷新的定时任务
this.instanceInfoReplicator.start(this.clientConfig.
getInitialInstanceInfoReplicationIntervalSeconds());
} else {
logger.info("Not registering with Eureka server per configuration");
}
}
上述代码主要启动了三个定时任务实现 服务信息拉取、服务续约、服务注册三个功能,前两者使用TimedSupervisorTask
(extends TimeTask 定时任务),主要实现run方法中 "future = this.executor.submit(this.task)" 所有我们的核心关注点是具体的task,后者InstanceInfoReplicator类实现Runnable
第一个定时任务负责从EurekaServer拉取注册信息->new CacheRefreshThread():CacheRefreshThread实现了Runnable,在run()方法中调用了refreshRegistry()方法,这个方法就是去EurekaServer拉取注册信息的方法 主要是使用EurekaHttpClient 提交Http请求来从Eureka Server 获取所有可用服务信息,拉取注册信息分两种:全量获取和增量获取。全量获取会去EurekaServer拉取所有注册的信息,拉取后在本地进行过滤并对服务状态为UP的注册信息进行缓存;增量获取会对拉取下来的注册信息与本地缓存进行合并后进行缓存。
第二个定时任务负责服务心跳/续约->new HeartbeatThread():HeartbeatThread实现了Runnable,在run()方法中调用了renew()方法,当租约不存在时候进行注册操作,也是使用Http请求来与Eureka Server服务端进行心跳
第三个定时任务复制向EurekaServer发送注册/刷新请求->new InstanceInfoReplicator():InstanceInfoReplicator实现了Runnable,在run()方法中向EurekaServer端发起服务注册/刷新请求,这个内容有点不太清楚参考 InstanceInfoReplicator实例详解
4、Eurake 服务实例获取慢的原因
Eureka 的服务提供者 在使用eureka client注册到eureka server是延迟注册的
5、eureka自我保护机制
通过配置 发现指定时间内服务续约 低于指定的值,会开启自我保护,不会对其注册的服务进行剔除操作,为了避免网络状况不佳的情况。
6、eureka 服务注册中心 是服务调用的核心组件,对于有大量微服务实例注册,所有我们需要构建 高可用,高可靠的eureka集群
7、eureka 设置服务端和客户端应用搭建过程中的问题:
maven依赖的问题,springBoot的版本和springCloud版本一定要进行匹配
maven依赖下载 需要设置阿里云仓库