本篇我们讲解eureka的心跳机制,来看看eureka的源码是通过怎样的设计来实现对服务的心跳检测的。
正式开始之前,先来思考一下eureka设计心跳机制是为了解决什么问题?
其实最主要的目的,就是通过心跳来达到对各个服务健康状态的监测。
也就是为了检测出宕机的服务,那些宕机的服务肯定是不能正常访问的,所以需要筛选出这些服务,并将这些不能正常提供访问支持的服务,从注册表中移除掉。
换句话讲,这些异常的服务都被下线啦。
所以本篇,我们从eureka的服务下线开始讲起。
服务怎么主动下线?
当我们重启服务,或者需要将某个服务关闭时,就需要将自己的服务下线,也就是告诉eureka-server:我要下线了,你记得把我的地址信息从注册表中删除!!
在关闭服务时,我们需要调用eureka-client提供DiscoveryClient.java
中的shutdown()
方法。
调用shutdown()
方法后,eureka-client会向eureka-server发送一个服务下线的http请求,eureka-server就会将发送请求的服务实例从注册表中移除。
eureka-client端代码比较简单,直接贴出源码看一下即可:
public synchronized void shutdown() {
//关闭定时任务
cancelScheduledTasks();
//通知eureka-server下线
unregister();
//其他..略..
}
void unregister() {
//发送http请求
EurekaHttpResponse<Void> httpResponse =
eurekaTransport.registrationClient.cancel(instanceInfo.getAppName(), instanceInfo.getId());
}
主要的业务逻辑在eureka-server端,通过前面两篇文章,其实我们已经知道eureka-server大概要做哪些工作了,比如:从注册表中删除下线的服务、将缓存数据过期、把变动的服务实例信息加入到Queue
中。
还是先上一张代码简图,查看一下源码的调用流程:
其中最主要的方法是internalCancel(..)
,贴出主要部分来看一看:
public abstract class AbstractInstanceRegistry implements InstanceRegistry {
//注册表:记录所有服务的ip和端口号等信息
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
= new ConcurrentHashMap<>();
//记录最近状态发生变化的服务实例
private ConcurrentLinkedQueue<RecentlyChangedItem> recentlyChangedQueue
= new ConcurrentLinkedQueue<>();
//服务下线主要方法
protected boolean internalCancel(String appName, String id, boolean isReplication) {
//根据服务名,获取对应的服务实例
Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
Lease<InstanceInfo> leaseToCancel = null;
if (gMap != null) {
//从注册表中删除下线的那一个服务实例
leaseToCancel = gMap.remove(id);
}
//记录下线时间
leaseToCancel.cancel();
//ActionType,客户端合并数据时会用到
instanceInfo.setActionType(ActionType.DELETED);
//向最近发生变化的队列添加数据
recentlyChangedQueue.add(new RecentlyChangedItem(leaseToCancel));
//清空缓存
invalidateCache(appName, vip, svip);
}
}
再次提醒: 文章中贴出来的代码,是经过提炼的,一些繁琐的if-else判断、log打印,或者是干扰理解的部分,文章中都没有贴进来。
主要是为了方便大家理解,也是为了增加文章的可读性。
AbstractInstanceRegistry.java
前文我们已经多次用到过,这里就不再过多介绍。
recentlyChangedQueue
和ConcurrentHashMap<> registry
同样多次重复过,分别是存储最近变化的服务实例数据、注册表。
ActionType.DELETED
在上一章客户端获取增量注册表后,本地有一个合并数据的过程,合并时根据ActionType
的不同进行不同的操作,简单了解一下即可。
invalidateCache()
在上一章讲解多级缓存机制时,同样也贴过源码和详细说明,这里就不再赘述了。
基本上服务主动下线的源码,只要认真学习过上两章的内容,这一小节就很容易理解了。
一张图,总结一下服务下线、缓存更新、eureka-client端感知到服务下线的过程:
心跳机制是怎么实现的?
首先,我们用一张图,来展示一下eureka心跳机制。
从图中我们可以看出,eureka-client每隔30s就会向服务端大喊一声:我还活着。
如果转换为编程语言的话,这个过程是怎样实现的呢?想一想。
其实很简单,每隔30s,自然是用定时任务来实现。大喊一声,则是调用eureka-server端的接口,我们来看看eureka-client的代码实现。
定时任务的定义在DiscoveryClient.java
中,不知道大家有没有注意到DiscoveryClientclent.java
是一个非常重要的类,我们在学习源码的过程中,经常会读到这里面的代码。
先看一下方法调用简图:
代码片段:
public class DiscoveryClient implements EurekaClient {
private void initScheduledTasks() {
scheduler.schedule(
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
//定时任务执行的内容
new HeartbeatThread()
),
//定时间隔 30s
renewalIntervalInSecs, TimeUnit.SECONDS);
}
//内部类(线程)
private class HeartbeatThread implements Runnable {
public void run() {
//renew() 发送http请求
if (renew()) {
lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
}
}
}
}
eureka-client部分的代码比较简单,我们看到此处即可。
接下来看看eureka-server的处理。
先看一下方法调用的简图:
其中最主要的方法是Lease.java
中的renew()
,贴出源码看一下:
public class Lease<T> {
private volatile long lastUpdateTimestamp;
public void renew() {
//更新时间戳
lastUpdateTimestamp = System.currentTimeMillis() + duration;
}
}
没错,其实eureka-server只是更新了一下发送心跳信息的服务的最后更新时间lastUpdateTimestamp
。
至此,eureka的整个心跳机制就已经讲解完毕。
但是心跳机制只是方法,不是目的,设计心跳机制的目的是为了监测异常的服务,从而让eureka-server能够达到自动感知故障的目的。
下一篇,我们来看看eureka-server是怎么实现对服务的故障感知的。