我们前面几篇文章已经介绍了心跳信息类BeatInfo 以及 何时创建心跳任务及心跳任务的执行时间等,有了前面的知识基础 理解BeatReactor起来会简单很多
目录
学习目标: 了解心跳任务的生命周期及心跳是如何执行的。
初始化
public class BeatReactor {
....
private ScheduledExecutorService executorService;
private NamingProxy serverProxy;
//存储当前要执行的心跳任务
public final Map<String, BeatInfo> dom2Beat = new ConcurrentHashMap<String, BeatInfo>
();
....
public BeatReactor(NamingProxy serverProxy) {
this(serverProxy, UtilAndComs.DEFAULT_CLIENT_BEAT_THREAD_COUNT);
}
.....
public BeatReactor(NamingProxy serverProxy, int threadCount) {
this.serverProxy = serverProxy;
//心跳发送者线程池
executorService = new ScheduledThreadPoolExecutor(threadCount, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(true);
thread.setName("com.alibaba.nacos.naming.beat.sender");
return thread;
}
});
}
....
}
BeatReactor初始化时创建了一个定时执行心跳任务的线程池,线程池核心线程数大小默认是cpu核心数 / 2,同时创建dom2Beat 对象用于存储当前待执行的心跳任务
另外初始化了NamingProxy 变量用于心跳请求的发送。
添加心跳任务
public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
String key = buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());
BeatInfo existBeat = null;
//同样key的心跳保留最新的一个
if ((existBeat = dom2Beat.remove(key)) != null) {
existBeat.setStopped(true);
}
dom2Beat.put(key, beatInfo);
executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(),
TimeUnit.MILLISECONDS);
MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
}
private String buildKey(String serviceName, String ip, int port) {
return serviceName + Constants.NAMING_INSTANCE_ID_SPLITTER
+ ip + Constants.NAMING_INSTANCE_ID_SPLITTER + port;
}
代码容易理解删除历史旧的心跳任务(以 服务名和ip+端口 为key) 添加心跳任务到定时器执行
移除心跳任务
public void removeBeatInfo(String serviceName, String ip, int port) {
NAMING_LOGGER.info("[BEAT] removing beat: {}:{}:{} from beat map.", serviceName,
ip, port);
BeatInfo beatInfo = dom2Beat.remove(buildKey(serviceName, ip, port));
if (beatInfo == null) {
return;
}
beatInfo.setStopped(true);
MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
}
首先从map中移除,然后设置心跳对象的stopped标志为true
执行心跳任务
class BeatTask implements Runnable {
BeatInfo beatInfo;
public BeatTask(BeatInfo beatInfo) {
this.beatInfo = beatInfo;
}
@Override
public void run() {
if (beatInfo.isStopped()) {
return;
}
long result = serverProxy.sendBeat(beatInfo);
long nextTime = result > 0 ? result : beatInfo.getPeriod();
executorService.schedule(new BeatTask(beatInfo), nextTime,
TimeUnit.MILLISECONDS);
}
}
这里首先会判断是不是旧的心跳任务,是的话就舍弃掉。心跳发送成功后如果服务端返回【clientBeatInterval】字段有值,则以服务端返回的心跳间隔时间为准,否则把beatInfo里面的心跳间隔时间作为参数 再次放入定时器 等待下次执行。
Instance的心跳时间时怎么来的呢?
配置在instance的metaData中的属性【preserved.heart.beat.interval】值
为什么要以服务端的心跳时间为准呢?
服务端的心跳其实也是从服务的实例中的元数据中获取的,服务端有一个client的健康检查,检查时间也是配置在元数据的属性【preserved.heart.beat.timeout】中, 实际上配置在客户端个人觉得也是可以的,但是心跳时间及心跳超时配置在服务端方便集群的统一管理。客户端也不需要单独配置这个参数以及避免了错误配置带来的问题
心跳时间默认是5秒钟一次
关于MetricsMonitor
目前为止我们看到很多地方的代码有使用到类 MetricsMonitor ,从名字可以看出来是一个指标收集的统一处理类,读者可以了解下 Prometheus 的指标收集。
public class MetricsMonitor {
private static Gauge nacosMonitor = Gauge.build()
.name("nacos_monitor").labelNames("module", "name")
.help("nacos_monitor").register();
private static Histogram nacosClientRequestHistogram =
Histogram.build().labelNames("module", "method", "url", "code")
.name("nacos_client_request").help("nacos_client_request")
.register();
public static Gauge.Child getServiceInfoMapSizeMonitor() {
return nacosMonitor.labels("naming", "serviceInfoMapSize");
}
public static Gauge.Child getDom2BeatSizeMonitor() {
return nacosMonitor.labels("naming", "dom2BeatSize");
}
public static Gauge.Child getListenConfigCountMonitor() {
return nacosMonitor.labels("naming", "listenConfigCount");
}
public static Histogram.Timer getConfigRequestMonitor(String method, String url,
String code) {
return nacosClientRequestHistogram.labels("config", method, url,
code).startTimer();
}
public static Histogram.Child getNamingRequestMonitor(String method, String url,
String code) {
return nacosClientRequestHistogram.labels("naming", method, url, code);
}
}
总结
客户端的心跳就是通过维护一个待执行心跳任务的map保证心跳任务的最新性
通过schedule线程池定时执行心跳任务,每次心跳任务处理后需要手动塞入新心跳任务。
细心的同学可能看出来了如果发送心跳的方法执行异常了是不是后面【
executorService.schedule(new BeatTask(beatInfo), nextTime,
TimeUnit.MILLISECONDS)】的语句就不执行了呢,这个大家可以回头看前一篇问题找到答案。