一 核心原理流程图
二 客户端完成对服务端的监听
2.1 实现udp监听
PushReceiver的构造方法中会触发监听器的初始化
public PushReceiver(HostReactor hostReactor) {
try {
this.hostReactor = hostReactor;
this.udpSocket = new DatagramSocket();
this.executorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(true);
thread.setName("com.alibaba.nacos.naming.push.receiver");
return thread;
}
});
// 触发监听器
this.executorService.execute(this);
} catch (Exception e) {
NAMING_LOGGER.error("[NA] init udp socket failed", e);
}
}
2.2 监听服务端实例变化
在PushReceiver的run方法中,不断循环监听服务端的push请求。然后调用 processServiceJSON 对服务端的数据进行解析
@Override
public void run() {
while (!closed) {
try {
// byte[] is initialized with 0 full filled by default
byte[] buffer = new byte[UDP_MSS];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
// 接受服务端的数据变化
udpSocket.receive(packet);
String json = new String(IoUtils.tryDecompress(packet.getData()), UTF_8).trim();
NAMING_LOGGER.info("received push data: " + json + " from " + packet.getAddress().toString());
PushPacket pushPacket = JacksonUtils.toObj(json, PushPacket.class);
String ack;
if ("dom".equals(pushPacket.type) || "service".equals(pushPacket.type)) {
// 监听到有变化的事件 触发本地缓存列表的变化
hostReactor.processServiceJson(pushPacket.data);
// send ack to server
ack = "{\"type\": \"push-ack\"" + ", \"lastRefTime\":\"" + pushPacket.lastRefTime + "\", \"data\":"
+ "\"\"}";
} else if ("dump".equals(pushPacket.type)) {
// dump data to server
ack = "{\"type\": \"dump-ack\"" + ", \"lastRefTime\": \"" + pushPacket.lastRefTime + "\", \"data\":"
+ "\"" + StringUtils.escapeJavaScript(JacksonUtils.toJson(hostReactor.getServiceInfoMap()))
+ "\"}";
} else {
// do nothing send ack only
ack = "{\"type\": \"unknown-ack\"" + ", \"lastRefTime\":\"" + pushPacket.lastRefTime
+ "\", \"data\":" + "\"\"}";
}
//
udpSocket.send(new DatagramPacket(ack.getBytes(UTF_8), ack.getBytes(UTF_8).length,
packet.getSocketAddress()));
} catch (Exception e) {
if (closed) {
return;
}
NAMING_LOGGER.error("[NA] error while receiving push data", e);
}
}
}
三 服务端建立健康监控
还记得在服务提供者发起服务注册时。在 createEmptyService 方法中,会创建一个空的服务
并且在这个创建过程中,调用了一个 putServiceAndInit ,这个方法中除了创建空的服务并且初始
化,还会调用 service.init 方法进行服务的初始化
这个init方法,会和当前服务提供者建立一个心跳检测机制,这个心跳检测会每5s执行一次
public void init() {
HealthCheckReactor.scheduleCheck(clientBeatCheckTask);
for (Map.Entry<String, Cluster> entry : clusterMap.entrySet()) {
entry.getValue().setService(this);
entry.getValue().init();
}
}
那我们来看看ClientBeatCheckTask里面是怎样实现的
@Override
public void run() {
try {
// If upgrade to 2.0.X stop health check with v1
if (ApplicationUtils.getBean(UpgradeJudgement.class).isUseGrpcFeatures()) {
return;
}
if (!getDistroMapper().responsible(service.getName())) {
return;
}
if (!getSwitchDomain().isHealthCheckEnabled()) {
return;
}
List<Instance> instances = service.allIPs(true);
// first set health status of instances:
for (Instance instance : instances) {//遍历服务节点进行心跳检测
//如果服务实例的最后一次心跳时间大于设置的超时时间,则认为这个服务已经下线。
if (System.currentTimeMillis() - instance.getLastBeat() > instance.getInstanceHeartBeatTimeOut()) {
if (!instance.isMarked()) {
if (instance.isHealthy()) {
instance.setHealthy(false);
Loggers.EVT_LOG
.info("{POS} {IP-DISABLED} valid: {}:{}@{}@{}, region: {}, msg: client timeout after {}, last beat: {}",
instance.getIp(), instance.getPort(), instance.getClusterName(),
service.getName(), UtilsAndCommons.LOCALHOST_SITE,
instance.getInstanceHeartBeatTimeOut(), instance.getLastBeat());
//推送服务变更事件。
getPushService().serviceChanged(service);
}
}
}
}
if (!getGlobalConfig().isExpireInstance()) {
return;
}
// then remove obsolete instances:
for (Instance instance : instances) {
if (instance.isMarked()) {
continue;
}
if (System.currentTimeMillis() - instance.getLastBeat() > instance.getIpDeleteTimeout()) {
// delete instance
Loggers.SRV_LOG.info("[AUTO-DELETE-IP] service: {}, ip: {}", service.getName(),
JacksonUtils.toJson(instance));
//删除过期的服务实例
deleteIp(instance);
}
}
} catch (Exception e) {
Loggers.SRV_LOG.warn("Exception while processing client beat time out.", e);
}
}
监听服务状态变更事件,然后遍历所有的客户端,通过udp协议进行消息的广播通知
UdpPushService.onApplicationEvent
@Override
public void onApplicationEvent(ServiceChangeEvent event) {
// ......
udpPush(ackEntry);
}
那么服务消费者此时应该是建立了一个udp服务的监听,否则服务端无法进行数据的推送。这个监听是
在HostReactor的构造方法中初始化的
三 总结
- nacos在注册实例时调用createEmptyService()->createServiceIfAbsent()->putServiceAndInit()->init()时会触发健康检测,没个五秒执行一次(ClientBeatCheckTask中的run())
- ClientBeatCheckTask中的run()会遍历注册中心的所有实例,根据当最后一次心跳时间和默认心跳超时时间的差值判断实例是否健在,判断实例不存在时会发布一个ServiceChangeEvent事件,并将当前实例从注册中心删除
- UdpPushService监听到ServiceChangeEvent通过upd协议推送消息个服务端
- 客户端在初始化HostReactor会通过PushReceiver触发upd消息的监听器
- 客户端监听到来自服务端的服务实例变化消息时,会更新当前本地缓存