如果服务端没有考虑到各种异常场景,很难稳定运行,本章以生产环境 MQTT服务无法提供接入服务为例,详细介绍MQTT服务和Netty在异常场景下的保护机制。
问题:
生产环境的MQTT服务运行一段时间之后,发现新的端侧设备无法接入,连接超时。分析MQTT服务端日志,没有明显的异常,但是内存占用较高,
查看连接数,发现有数十万个TCP连接处于ESTABLISHED状态,实际的MQTT连接数应该在1万个左右,显然这么多连接肯定存在问题。由于MQTT服务端的内存是按照2万个左右连接数规模配置的,因此当连接数达到数十万个的规模之后,导致了服务端大量SocketChannel积压、内存暴涨、高频率GC和较长的STW时间,对端侧设备的接入造成了很大影响,部分设备MQTT握手超时,无法接入。
通过抓包分析发现,一些端侧设备并没有按照MQTT协议规范进行处理,包括:
(1)
客户端发起CONNECT连接,SSL握手成功之后没有按照协议规范继续处理,例如发送PING命令。
(2)
客户端发起TCP连接,不做SSL握手,也不做后续处理,导致TCP连接被挂起。
由于服务端是严格按照MQTT 规范实现的,上述端侧设备不按规范接入,实际上消息调度不到MQTT应用协议层。MQTT服务端依赖Keep Alive机制进行超时检测,当一段时间接收不到客户端的心跳和业务消息时,就会触发心跳超时,关闭连接
。针对上述两种接入场景
,由于MQTT的连接流程没有完成,MQTT协议栈不认为这个是合法的MQTT连接,因此心跳保护机制无法对上述TCP连接进行检测。客户端和服务端都没有主动关闭这个连接,导致TCP连接一直保持。
MQTT接入过程:
服务端需要做一些可靠性保护,以防止客户端非正常操作带来的无效连接暴增:
(1)端侧设备的 TCP连接接入后,启动一个链路检测定时器加入NioEventLoop。
(2)链路检测定时器一旦触发,就主动关闭TCP连接。
(3)TCP连接完成MQTT协议层的CONNECT之后,删除之前创建的链路检测定时器。