7. 服务端心跳检测流程
在 Nacos 中,用户可以通过两种方式进行临时实例的注册,通过 Nacos 的 OpenAPI 进行服务注册或通过 Nacos 提供的 SDK 进行服务注册。
- OpenAPI 的注册方式实际是用户根据自身需求调用 Http 接口对服务进行注册,然后通过 Http 接口发送心跳到注册中心。在注册服务的同时会注册⼀个全局的客户端心跳检测的任务。在服务⼀段时间没有收到来自客户端的心跳后,该任务会将其标记为不健康,如果在间隔的时间内还未收到心跳,那么该任务会将其剔除。
- SDK 的注册方式实际是通过 RPC 与注册中心保持连接(Nacos 2.x 版本中,旧版的还是仍然通过 OpenAPI 的方式),客户端会定时的通过 RPC 连接向 Nacos 注册中心发送心跳,保持连接的存活。如果客户端和注册中心的连接断开,那么注册中心会主动剔除该 client 所注册的服务,达到下线的效果。同时 Nacos 注册中心还会在注册中心启动时,注册⼀个过期客户端清除的定时任务,用于删除那些健康状态超过⼀段时间的客户端。
服务端在启动时,会在 EphemeralIpPortClientManager 中启动一个过期 client 清理器
// EphemeralIpPortClientManager
public EphemeralIpPortClientManager(DistroMapper distroMapper, SwitchDomain switchDomain) {
this.distroMapper = distroMapper;
GlobalExecutor.scheduleExpiredClientCleaner(new ExpiredClientCleaner(this, switchDomain), 0,
Constants.DEFAULT_HEART_BEAT_INTERVAL, TimeUnit.MILLISECONDS);
clientFactory = ClientFactoryHolder.getInstance().findClientFactory(ClientConstants.EPHEMERAL_IP_PORT);
}
GlobalExecutor.scheduleExpiredClientCleaner 会每隔五秒执行一次 ExpiredClientCleaner 的 run 方法
private static class ExpiredClientCleaner implements Runnable {
private final EphemeralIpPortClientManager clientManager;
// 用于获取一些配置相关开关状态等信息的领域对象
private final SwitchDomain switchDomain;
// 构造函数,用于初始化ExpiredClientCleaner实例,接收客户端管理器和开关领域对象作为参数
public ExpiredClientCleaner(EphemeralIpPortClientManager clientManager, SwitchDomain switchDomain) {
this.clientManager = clientManager;
this.switchDomain = switchDomain;
}
// 实现Runnable接口的run方法,该方法定义了具体的清理过期客户端的逻辑
@Override
public void run() {
// 获取当前系统时间的毫秒数,用于后续与客户端的上次更新时间进行比较
long currentTime = System.currentTimeMillis();
// 遍历客户端管理器中的所有客户端ID
for (String each : clientManager.allClientId()) {
// 根据客户端ID从客户端管理器中获取对应的基于IP和端口的客户端实例
IpPortBasedClient client = (IpPortBasedClient) clientManager.getClient(each);
// 如果获取到的客户端实例不为空,并且通过isExpireClient方法判断该客户端已过期
if (null!= client && isExpireClient(currentTime, client)) {
// 调用客户端管理器的clientDisconnected方法,断开与该过期客户端的连接,进行清理操作
clientManager.clientDisconnected(each);
}
}
}
// 用于判断给定的客户端是否已过期的方法,接收当前时间和客户端实例作为参数
private boolean isExpireClient(long currentTime, IpPortBasedClient client) {
// 计算从当前时间到客户端上次更新时间的间隔时长
long noUpdatedTime = currentTime - client.getLastUpdatedTime();
// 判断客户端是否为临时客户端(通常临时客户端可能有特定的生命周期管理要求)
// 并且满足以下两个条件之一:
// 1. 同时满足发布客户端过期条件和订阅客户端过期条件
// 2. 距离上次更新时间超过了客户端配置中设定的客户端过期时间
return client.isEphemeral() && (
isExpirePublishedClient(noUpdatedTime, client) && isExpireSubscriberClient(noUpdatedTime, client)
|| noUpdatedTime > ClientConfig.getInstance().getClientExpiredTime());
}
// 用于判断发布客户端是否已过期的方法,接收未更新时间和客户端实例作为参数
private boolean isExpirePublishedClient(long noUpdatedTime, IpPortBasedClient client) {
// 如果客户端已发布的所有服务列表为空,并且未更新时间超过了默认的IP删除超时时间(这里设定为30秒)
// 则认为发布客户端已过期
return client.getAllPublishedService().isEmpty() && noUpdatedTime > Constants.DEFAULT_IP_DELETE_TIMEOUT;
}
// 用于判断订阅客户端是否已过期的方法,接收未更新时间和客户端实例作为参数
private boolean isExpireSubscriberClient(long noUpdatedTime, IpPortBasedClient client) {
// 如果客户端已订阅的所有服务列表为空,或者未更新时间超过了开关领域对象中设定的默认推送缓存毫秒数(这里设定为10秒)
// 则认为订阅客户端已过期
return client.getAllSubscribeService().isEmpty() || noUpdatedTime > switchDomain.getDefaultPushCacheMillis();
}
}
- ExpiredClientCleaner 的 run 方法会遍历 clientManager 的所有 client,然后判断 client 是否过期。
- 如果 client 过期,则会调用 clientManager 的 clientDisconnected 方法
private final ConcurrentMap<String, IpPortBasedClient> clients = new ConcurrentHashMap<>();
@Override
public boolean clientDisconnected(String clientId) {
Loggers.SRV_LOG.info("Client connection {} disconnect, remove instances and subscribers", clientId);
IpPortBasedClient client = clients.remove(clientId);
if (null == client) {
return true;
}
NotifyCenter.publishEvent(new ClientEvent.ClientDisconnectEvent(client, isResponsibleClient(client)));
client.release();
return true;
}
- clientDisconnected 方法将 client 从 clients 中删除,然后再发布 ClientEvent.ClientDisconnectEvent 方法。
- ClientEvent.ClientDisconnectEvent 会将 discounnectEvent 通知给客户端和其他的 Nacos 服务端。

浙公网安备 33010602011771号