扫码关注我的公众号
目录
Eureka是Netflix组件的一个子模块,也是核心模块之一。云端服务发现,一个基于 REST 的服务,用于定位服务,以实现云端中间层服务发现和故障转移(来源springcloud中文网的介绍:https://www.springcloud.cc/)。下图总结了Eureka服务端(以下简称服务端)与Eureka客户端(以下简称客户端)之间协同工作的流程:
流程说明:
- Eureka客户端(以下简称客户端)启动后,定时向Eureka服务端(以下简称服务端)注册自己的服务信息(服务名、IP、端口等);
- 客户端启动后,定时拉取服务端以保存的服务注册信息;
- 拉取服务端保存的服务注册信息后,就可调用消费其他服务提供者提供的服务。
虽然流程比较简单,但是在这样的简单流程下,Eureak究竟做了哪些工作,我们会有如下问题:
- 客户端启动时如何注册到服务端?
- 服务端如何保存客户端服务信息?
- 客户端如何拉取服务端已保存的服务信息(是需要使用的时候再去拉取,还是先拉取保存本地,使用的时候直接从本地获取)?
- 如何构建高可用的Eureka集群?
- 心跳和服务剔除机制是什么?
- Eureka自我保护机制是什么?
要彻底搞清楚Eureka的工作流程,必须需要弄清楚这些问题,也是面试中常遇到的问题,接下来我将结合源码的方式对这些问题一一解答,源码版本说明:
- springboot:2.2.1.RELEASE
- springcloud:Hoxton.SR1
- Eureka:1.9.13
1、客户端启动时如何注册到服务端
1.1、源码分析
Eureka客户端在启动后,会创建一些定时任务,其中就有一个任务heartbeatExecutor就是就是处理心跳的线程池,部分源码(源码位置:com.netflix.discovery.DiscoveryClient)如下:
heartbeatExecutor = new ThreadPoolExecutor(
1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
.setDaemon(true)
.build()
); // use direct handoff
...此处省略其他代码
//finally, init the schedule tasks (e.g. cluster resolvers, heartbeat, instanceInfo replicator, fetch
initScheduledTasks();
查看方法initScheduledTasks以及注释,可知该方法是初始化所有的任务(schedule tasks)。
/**
* Initializes all scheduled tasks.
*/
private void initScheduledTasks() {
...
// Heartbeat timer
scheduler.schedule(
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
),
renewalIntervalInSecs, TimeUnit.SECONDS);
...
}
在上述方法中,第15行创建了一个线程HeartbeatThread,该线程就是处理心跳任务:
/**
* The heartbeat task that renews the lease in the given intervals.
*/
private class HeartbeatThread implements Runnable {
public void run() {
if (renew()) {
lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
}
}
}
/**
* Renew with the eureka service by making the appropriate REST call
*/
boolean renew() {
EurekaHttpResponse<InstanceInfo> httpResponse;
try {
httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
logger.debug(PREFIX + "{} - Heartbeat status: {}", appPathIdentifier, httpResponse.getStatusCode());
if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
REREGISTER_COUNTER.increment();
logger.info(PREFIX + "{} - Re-registering apps/{}", appPathIdentifier, instanceInfo.getAppName());
long timestamp = instanceInfo.setIsDirtyWithTime();
boolean success = register();
if (success) {
instanceInfo.unsetIsDirty(timestamp);
}
return success;
}
return httpResponse.getStatusCode() == Status.OK.getStatusCode();
} catch (Throwable e) {
logger