目前,越来越多的业务场景需要使用长连接进行消息推送,以代替传统的轮询模式。本文主要介绍基于netty实现长连接推送服务的主要流程,并简单介绍其性能情况。
一、netty简介
由于长连接平台是基于netty构建的,因此首先对netty进行简单的介绍。netty是一款优秀的java网络编程框架。主要是对java原生的io/Nio包进行了二次封装;相较于java原生io,netty在健壮性、性能、可定制性和可扩展性等方面更优秀,api也更简单易用。与java原生NIO类似,netty也分为client端和server端。
netty server端示例代码
示例中代码虽少,却包含了server端启动的主要流程,也是netty实践reactor模型的体现。
按照功能,整个流程可以分为五个主要步骤:
设置server端启动参数。
绑定端口,并启动服务。该操作会在boss线程池中注册一个NioServerSocketChannel对象,之后boss线程开始监听客户端连接请求。
client发起连接请求。boss线程池会不断循环判断是否有连接请求;若有连接请求,ACCEPT事件触发。
ACCEPT事件触发后,由boss线程池创建NioSocketChannel对象(代表一个tcp连接),在经过参数设置等初始化步骤之后,将该对象注册到worker线程池。
worker线程池则不断检测其管理的NioSocketChannel对象是否有读写事件就绪,若有就绪事件则调用对应的ChannelHandler进行处理。
上述示例中,没有用户自定义的线程池。在实际应用,一些耗时的操作如权限验证、读写数据库、复杂的业务逻辑,往往会放在业务线程池中处理,避免阻塞netty的线程池。
二、系统架构
长连接平台提供包括单推、多推和广播三种类型的推送。单推是针对单个设备的推送;多推针对同一账号的多个设备;广播则是一组设备,设备、账号都可以不同。长连接系统架构图
说明:
最上面为nginx:当有http请求过来时,会转发到长连接平台的一个节点上;
左边为日志系统和监控模块:日志模块负责业务日志的输出,主要包括设备注册、topic订阅、推送、监控等日志;监控模块监控的内容包括:连接数(建立和断开)、订阅qps、推送qps、gc情况、各种线程池任务堆积情况等;
节点管理模块用于完成节点的初始化:包括绑定端口、监听IO事件;当节点启动时,向zk写入节点信息(ip和端口号),构成集群;从mysql读取产品信息,用于业务端认证;
连接管理模块:实现连接的建立和维护、数据的编解码、心跳和超时机制、任务分发机制;
业务线程池用于执行由连接管理器分发的任务。client端主要命令有:设备注册、主题订阅和连接关闭;业务端主要命令有:认证、发布命令和查询命令;节点之间的通信命令主要是发布命令;当设备不在线时,用redis对单推和多推类型的消息进行缓存。
三、服务间的关系
整个长连接平台设计到三方通信:业务系统(信息提供者)、长连接平台和client(信息消费者)。当信息提供者有信息需要发布时,会将消息发送到长连接平台,然后由长连接平台选择符合条件的消费者进行推送。 按照功能,连接主要分为:业务端与server之间的连接(tcp、http两种方式)、client与server之间的连接(主要是tcp、websocket,http辅助)以及server不同节点之间的连接(tcp)。
四、数据格式
根据场景不同,长连接平台采用了不同的数据格式:对于http和websocket,采用json格式;对于tcp连接采用protobuf协议。
五、主要流程
服务启动之后,即开始监听client和业务系统的请求。
5.1 client 主要流程
client的主要请求包括:获取节点信息、建立连接、设备注册、订阅/取消订阅主题和关闭连接。
5.1.1 节点信息获取
通常一个设备只需与长连接平台建立一个长连接即可。但长连接平台是由多个节点组成的集群,集群中的节点可能会动态加入或移出,因此需要负载均衡策略,来保证每个节点处理的任务相当。节点信息获取过程就是集群中实现负载均衡的过程。server启动后,会向zk注册该节点的信息(ip、端口号等),集群中的每个节点都会维护一个一致性hashmap,用于存放各个节点的信息;client发送获取节点信息请求时,会携带deviceId参数,根据deviceId的哈希值获取一个节点的ip和端口,并返回给client。
5.1.2 设备注册
经过获取节点信息后,client端发起连接请求,并向该节点注册设备信息。
设备注册流程
5.1.3 主题订阅/取消订阅
经过注册后,client与平台之间建立了长连接。然后根据与业务后端的约定,订阅相关的topic;当业务后端发布消息时即可收到。
订阅流程
5.1.4 连接关闭与重连
当由于网络异常等导致连接断开时,可以重新发起设备注册命令并订阅相关主题;服务器端检测到连接断开后会进行连接上下文的清理工作。
5.2 业务端流程
业务后台向长连接平台发送的命令主要包括:认证、发布、查询(只适用于单推和多推)和关闭连接。
5.2.1 发布流程
业务端从zk中获取可用节点列表,然后经认证后与长连接平台建立长连接;
当业务端有消息需要推送时,通过tcp连接发送到长连接平台;平台接收到发布消息命令后,根据推送类型、topic等找到连接集合,向集合中每一个连接进行推送;对于单推,设备不在线则对消息进行缓存(写入redis);对于多推,当所有设备都不在线时,对消息进行缓存;若连接不在该节点,则通过节点间通信,调用其他节点进行推送。对于一个具体连接而言,推送过程会涉及到对消息编码、封包等一系列过程。
消息发布流程
六、 压测数据
长连接服务最主要的目标有两个:更高的连接数和更高的qps。在NIO模式下,netty可以轻松挂起几十万连接,维持其心跳消耗的cpu、带宽等资源也非常少。因此,最主要的是如何实现更高的qps。 在某业务场景下,一个用户会订阅几十到上百个topic,业务端每秒钟会推送几千条消息。因此,测试时业务端固定在5000条/s(即业务端有5000个topic,每个topic每秒发一条数据)。client订阅M个topic,即从5000个topic中随机选取M个并订阅。 由于涉及到的流程较为复杂,一般的压测工具难以胜任,故使用netty client模式编写压测代码来模拟业务端和应用client。当有连接激活、读取数据等事件时进行相关业务处理。
压测 client端 示例代码
机器性能:aws机器2核、2.90GHz、内存4G。
tcp协议压测结果:
websocket协议压测结果:
可以看出使用netty推送,消息qps可达10w级别;tcp相较于socketIO这种上层协议优势明显。
其他注意事项
设置linux最大打开文件数(Max Open Files)。
根据业务特点,设置tcp读写缓冲区大小。
由于linux端口数限制,模拟client的压测服务器最多只能挂65535个连接,需多台机器压测。
根据业务特点设置合理的JVM启动参数,如各种内存区域、直接内存大小等。
生产环境中,注意日志对性能的影响;使用合理的日志框架,并设置合适的日志级别。
参考资料:
https://github.com/mrniko/netty-socketio
http://netty.io/
乐得技术∣乐得美好生活
领取专属 10元无门槛券
私享最新 技术干货