图解+源讲解 Nacos 客户端发起心跳请求


人的才华就如海绵的水,没有外力的挤压,它是绝对流不出来的。流出来后,海绵才能吸收新的源泉


从哪里开始分析

    其实想想就知道,指定是在客户端发起注册的时候就会在本地有一个后台的线程在进行维护心跳发送请求,那么就是在注册时候指定有一个心跳任务的创建

@Override
public void registerInstance(String serviceName, String groupName, Instance instance)
throws NacosException {
NamingUtils.checkInstanceIsLegal(instance);
String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
if (instance.isEphemeral()) {
// 构建心跳信息
BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
// 心跳执行器
beatReactor.addBeatInfo(groupedServiceName, beatInfo);
}
serverProxy.registerService(groupedServiceName, groupName, instance);
}

    这么看就是在进行实例注册的时候,构建的心跳信息,之后放入了心跳执行器的里面进行任务执行

构建心跳信息

    创建心跳信息,里面包含了服务名称、IP地址、端口号、集群名称、权重、元数据信息、心跳发送频率,这里面的心跳发送频率默认是5秒

public BeatInfo buildBeatInfo(String groupedServiceName, Instance instance) {
// 创建心跳信息
BeatInfo beatInfo = new BeatInfo();
// 设置服务名称
beatInfo.setServiceName(groupedServiceName);
// 设置IP地址
beatInfo.setIp(instance.getIp());
// 设置端口号
beatInfo.setPort(instance.getPort());
// 设置集群名称
beatInfo.setCluster(instance.getClusterName());
// 设置权重
beatInfo.setWeight(instance.getWeight());
// 设置元数据
beatInfo.setMetadata(instance.getMetadata());
beatInfo.setScheduled(false);
// 设置频率
beatInfo.setPeriod(instance.getInstanceHeartBeatInterval());
return beatInfo;
}

构建心跳执行器

    创建了一个定时调度线程池执行器,里面创建的是一个后台的守护线程、并且名字是:

com.alibaba.nacos.naming.beat.sender

public BeatReactor(NamingProxy serverProxy, int threadCount) {
this.serverProxy = serverProxy;
this.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;
}
});
}

向心跳执行器中放入心跳信息

public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
// 构建key
String key = buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());
BeatInfo existBeat = null;
//fix #1733
if ((existBeat = dom2Beat.remove(key)) != null) {
existBeat.setStopped(true);
}
// 将心跳信息放入 dom2Beat 中
dom2Beat.put(key, beatInfo);
// 进行心跳任务创建之后放入调度执行器中执行,默认是5秒
executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(),
TimeUnit.MILLISECONDS);
// 指标信息监控
MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
}

1. 心跳key的构建

    心跳key = 服务名称+#+ip地址+#+端口号

public String buildKey(String serviceName, String ip, int port) {
return serviceName + Constants.NAMING_INSTANCE_ID_SPLITTER +
ip + Constants.NAMING_INSTANCE_ID_SPLITTER + port;
}

2. 将key和心跳信息放入到dom2Beat Map 中

public final Map<String, BeatInfo> dom2Beat = new ConcurrentHashMap<String, BeatInfo>();

3. 创建心跳任务并执行

class BeatTask implements Runnable {

BeatInfo beatInfo;
public BeatTask(BeatInfo beatInfo) {
this.beatInfo = beatInfo;
}

@Override
public void run() {
// 心跳是否停止如果是的话就返回
if (beatInfo.isStopped()) {
return;
}
// 获取心跳频率
long nextTime = beatInfo.getPeriod();
try {
// 发送心跳请求并返回
JsonNode result = serverProxy.sendBeat(beatInfo,
BeatReactor.this.lightBeatEnabled);
// 从返回的结果里面获取心跳频率
long interval = result.get("clientBeatInterval").asLong();
boolean lightBeatEnabled = false;
if (result.has(CommonParams.LIGHT_BEAT_ENABLED)) {
lightBeatEnabled = result.get(CommonParams.LIGHT_BEAT_ENABLED)
.asBoolean();
}
BeatReactor.this.lightBeatEnabled = lightBeatEnabled;
if (interval > 0) {
nextTime = interval;
}
// code = 10200
int code = NamingResponseCode.OK;
// 返回的结果信息里面是否有 code 如果有的话则获取
if (result.has(CommonParams.CODE)) {
code = result.get(CommonParams.CODE).asInt();
}
// 如果没有找到请求资源 20404那么就创建一个实例进行注册
if (code == NamingResponseCode.RESOURCE_NOT_FOUND) {
// 创建一个实例
Instance instance = new Instance();
// 设置端口号
instance.setPort(beatInfo.getPort());
// 设置IP
instance.setIp(beatInfo.getIp());
// 设置权重
instance.setWeight(beatInfo.getWeight());
// 设置元数据信息
instance.setMetadata(beatInfo.getMetadata());
// 设置集群名称
instance.setClusterName(beatInfo.getCluster());
// 设置服务名称
instance.setServiceName(beatInfo.getServiceName());
// 设置实例ID
instance.setInstanceId(instance.getInstanceId());
// 设置是虚拟节点
instance.setEphemeral(true);
try {
// 发起服务注册
serverProxy.registerService(beatInfo.getServiceName(),
NamingUtils.getGroupName(beatInfo.getServiceName()), instance);
} catch (Exception ignore) {
}
}
}
// 心跳任务执行
executorService.schedule(new BeatTask(beatInfo), nextTime,
TimeUnit.MILLISECONDS);
}
}

心跳请求发起

    serverProxy.sendBeat(beatInfo,BeatReactor.this.lightBeatEnabled) 这句代码进行了心跳发送请求,请求的路径是:​​http://localhost:8848/nacos/v1/ns/instance/beat​

public JsonNode sendBeat(BeatInfo beatInfo, boolean lightBeatEnabled)
throws NacosException {

if (NAMING_LOGGER.isDebugEnabled()) {
NAMING_LOGGER.debug("[BEAT] {} sending beat to server: {}",
namespaceId, beatInfo.toString());
}
// 构建参数进行请求
Map<String, String> params = new HashMap<String, String>(8);
Map<String, String> bodyMap = new HashMap<String, String>(2);
if (!lightBeatEnabled) {
bodyMap.put("beat", JacksonUtils.toJson(beatInfo));
}
//放入一些参数,比如名称空间等
params.put(CommonParams.NAMESPACE_ID, namespaceId);
params.put(CommonParams.SERVICE_NAME, beatInfo.getServiceName());
params.put(CommonParams.CLUSTER_NAME, beatInfo.getCluster());
params.put("ip", beatInfo.getIp());
params.put("port", String.valueOf(beatInfo.getPort()));
// 发起请求
// 请求路径是 http://localhost:8848/nacos/v1/ns/instance/beat
String result = reqApi(UtilAndComs.nacosUrlBase + "/instance/beat",
params, bodyMap, HttpMethod.PUT);
return JacksonUtils.toObj(result);
}

小结

    其实就是在客户端实例进行注册的时候,创建了心跳信息 BeatInfo ,之后将这个心跳信息放入了一个dom2Beat Map中,之后又创建了一个心跳任务,并将其放入了定时调度器进行周期执行心跳请求的访问