注册中心为什么要做健康查询机制

41 阅读3分钟

由于业务需要,我们搞了一套定制化服务注册逻辑,主要是服务端作为注册中心(在实际工作需求中没有必要进行解耦,所以两个揉在一起了)将客户端的信息收集起来,后续根据在线情况,将消息分发下去。

背景

技术选型很简单,客户端上线后,使用websocket与服务端注册中心建立长连接,服务端收到连接请求,在连接后,放入map中表示注册成功并为在线状态,下线也很简单,客户端下线后,服务端将其在map中移除,表示客户端注销并下线。注册中心服务端使用的是spring-websocket模块进行实现,主要逻辑如下图:

image.png

问题分析

这样的设计极为简单也看似很合理,然而在一段时间的运行中,服务端在给某一个认为“在线”(注册表中有)的客户端下发消息时,报错"socket closed",经过排查,发现该客户端也确实下线了。 afterConnectionClosed方法作为下线的唯一入口,必然是因为没有正常触发这个方法。那触发回调afterConnectionClosed方法的原因是什么呢?

其实很容易想到是这个websocket通道断开连接触发的,websocket协议以tcp协议进行实现,所以自然是tcp的fin包,当客户端下线时,断开tcp通道,内核会对服务端发送fin包,tomcat的poller线程会捕获到这个read类型的key,然后丢到线程池,线程池进行逻辑处理(这里顺便提一下,tomcat的nio不是标准的reactor主从,而是分为acceptor、poller以及逻辑处理线程池,acceptor负责监听并处理accept事件,poller负责监听处理read和write事件)。

那也就是说造成这个事故的原因是因为fin包并没有发出来,导致没有下线成功。那什么情况会使得客户端所在主机的内核没有发出来这个fin包呢?其中的一种复现方案就是直接拔客户端所在主机的网线。

解决方案

工程上的问题找解决方案很简单,就是照抄,于是我去搂了一眼其他项目中常用的注册中心nacos是怎么做的。我们的业务模型更适配nacos对临时节点(Ephemeral Instances)的处理方案,即AP模式,心跳机制为客户端定时上报心跳包,健康监测为服务端定时扫注册表中客户端上次的心跳时间是否超过健康阈值。

拉了下nacos2.5.1的源码debug了下,临时节点的注册以及心跳都是通过grpc进行通讯的,默认是5s一次心跳包推送。如果超出设定的健康周期没有推送心跳,则将其标记为不健康或者移除。最终我们也是对项目进行了类似nacos临时节点注册机制的改造。