我们前面几篇文章已经介绍了心跳信息类BeatInfo 以及 何时创建心跳任务及心跳任务的执行时间等,有了前面的知识基础 理解BeatReactor起来会简单很多
目录
初始化方法
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;
}
});
}
创建了一个定时执行心跳任务的线程池,同时初始化了NamingProxy 对象用于后面的发送请求。
线程池核心线程数大小默认是cpu核心数 / 2
添加心跳任务
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;
//移除老的 添加新的
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());
}
执行心跳任务
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里面的心跳间隔时间作为参数 再次放入定时器。
心跳时间默认是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);
}
}
总结
BeatReactor 类使用了 一个Map,保存每次新添加的心跳任务,同时删除老的心跳任务并设置老的心跳任务的stopped状态, 这样保证每个服务的示例只有一个心跳任务执行。
public final Map<String, BeatInfo> dom2Beat = new ConcurrentHashMap<String, BeatInfo>();