本文由哔哩哔哩资深开发工程师黄山成分享,原题“千万长连消息系统”,本文进行了排版和内容优化等。
1、引言
在当今数字娱乐时代,弹幕已经成为直播平台上不可或缺的互动元素之一。
用户通过发送弹幕、送礼等,可以实时在直播画面上展现自己的想法、评论和互动内容,从而丰富了用户观看体验。在这个过程中,实时向终端推送互动信息,就需要用到长连接。
长连接,顾名思义,是应用存活期间和服务端一直保持的网络数据通道,能够支持全双工上下行数据传输。其和请求响应模式的短连接服务最大的差异,在于它可以提供服务端主动给用户实时推送数据的能力。
本文将介绍B站基于golang实现的千万级长连接实时消息系统的架构设计与实践,包括长连接服务的框架设计,以及针对稳定性与高吞吐做的相关优化。
技术交流:
- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》
- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK(备用地址点此)
(本文已同步发布于:http://www.52im.net/thread-4647-1-1.html)
2、关联文章
- 《B站基于微服务的API网关从0到1的演进之路》
- 《石墨文档单机50万WebSocket长连接架构实践》
- 《百度统一socket长连接组件从0到1的技术实践》
- 《探探的IM长连接技术实践:技术选型、架构设计、性能优化》
- 《爱奇艺WebSocket实时推送网关技术实践》
- 《LinkedIn的Web端即时通讯实践:实现单机几十万条长连接》
- 《一个基于长连接的安全可扩展的订阅/推送服务实现思路》
- 《魅族2500万长连接的实时消息推送架构的技术实践分享》
- 《专访魅族架构师:海量长连接的实时消息推送系统的心得体会》
3、架构设计
3.1概述
长连接服务是多业务方共同使用一条长连接。
因为在设计时,需要考虑到不同业务方、不同业务场景对长连接服务的诉求,同时也要考虑长连接服务的边界,避免介入业务逻辑,影响后续长连接服务的迭代和发展。
长连接服务主要分为三个方面:
- 1)长连接建立、维护、管理;
- 2)下行数据推送;
- 3)上行数据转发(目前只有心跳,还没实际业务场景需求)。
3.2整体架构
长连接服务整体构架如上图所示,整体服务包含以下几个部分。
1)控制层:建连的前置调用,主要做接入合法性校验、身份校验和路由管控。
主要职责:
- 1)用户身份鉴权;
- 2)加密组装数据,生成合法token;
- 3)动态调度分配接入节点。
2)接入层:长连接核心服务,主要做卸载证书、协议对接和长连接维护。
主要职责:
- 1)卸载证书和协议;
- 2)负责和客户端建立并维护连接,管理连接id和roomid的映射关系;
- 3)处理上下行消息。
3)逻辑层:简化接入层,主要做长连的业务功能。
主要职责:
- 1)在线人数上报记录;
- 2)记录连接ID各属性和各节点的映射关系。
- 4)消息分发层:消息推送到接入层。
主要职责:
- 1)消息封装、压缩和聚合推送给相应的边缘节点;
5)服务层:业务服务对接层,提供下行消息推送入口。
主要职责:
- 1)管控业务推送权限;
- 2)消息检测和重组装;
- 3)消息按一定策略限流,保护自身系统。
3.3核心流程
长连接主要是3个核心流程:
- 1)建立连接:由客户端发起,先通过控制层,获取该设备合法的token和接入点配置;
- 2)维持连接:主要是客户端定时发起心跳,来保证长连接活跃;
- 3)下行推送:下行推送由业务Server发起,经由服务层根据相关标识确定连接标识和接入节点,经过消息分发层,把推送到对应的接入层,写入到指定连接上,然后下发到客户端。
3.4功能列表
结合B站业务场景,下行数据推送,提供如下通用功能:
- 1)用户级消息:指定推送给某些用户(比如给某个主播发送邀请pk消息);
- 2)设备级消息:制定推送给某些设备(比如针对未登陆的设备,推送客户端日志上报指令);
- 3)房间级消息:给某房间内的连接推送消息(比如给直播间的所有在线用户推送弹幕消息);
- 4)分区消息:给某分区的房间推送消息(比如给某个分区下,所有开播的房间,推送某个营收活动);
- 5)全区消息:给全平台用户推送消息(比如给全部在线用户推送活动通知)。
4、高吞吐技术设计
随着业务发展壮大,在线用户越来越多,长连系统的压力越来越大,尤其是热门赛事直播,比如s赛期间,全平台在线人数快达到千万,消息吞吐量有上亿,长连系统消息分发平均延迟耗时在1s左右,消息到达率达到99%,下面具体分析下长连做了哪些措施。
4.1网络协议
选择合适的网络协议对于长连接系统的性能至关重要:
- 1)TCP协议:可以提供可靠的连接和数据传输,适用于对数据可靠性要求较高的场景;
- 2)UDP协议:是一个不可靠的协议,但是传输效率高,适用于对数据可靠性要求不高的场景;
- 3)WebSocket协议:也是实现双向通信而不增加太多的开销,更多的用于web端。
接入层拆分成协议模块和连接模块:
- 1)协议模块:和具体的通讯层协议交互,封装不同通讯协议的接口和逻辑差异。
- 2)连接模块:维护长连接业务连接状态,支持请求上行、下行等业务逻辑,维护连接各属性,以及和房间id的绑定关系。
针对以上第 1)点,协议模块同时给连接模块提供统一的数据接口,包括连接建立、数据读取、写入等。后续增加新协议,只要在协议模块做适配,不影响其他模块的长连业务逻辑。
优势在于:
- 1)业务逻辑和通讯协议做了隔离,方便迭代增加通讯协议,简化兼容多通讯协议的实现难度;
- 2)控制层可以根据客户端的实际情况,下发更优的通讯协议。
4.2负载均衡
采用负载均衡技术可以将请求分发到不同的服务器节点上处理,避免了单一节点的负载过高,提高了系统的扩展性和稳定性。
长连增加控制层,做负载均衡。控制层提供http短连接口,基于客户端和各边缘节点实际情况,根据就近原则,动态选择合适的接入节点。
接入层支持水平扩展,控制层可以实时增加、减少分配节点。在S赛期间,在线人数快到达千万时,平衡调度各接入节点,保障了各节点的CPU和内存都在稳定的范围内。
4.3消息队列
消息推送链路是:业务发送推送,经过服务层推到边缘节点,然后下发给客户端。
服务层实时分发到各边缘节点,如果是房间类型消息,需要推到多个边缘节点,服务层同时还要处理业务逻辑,很影响消息的吞吐量。
所以增加消息队列和消息分发层,消息分发层维护各边缘节点信息和推送消息,提高了系统的并发处理能力和稳定性,避免了因消息推送阻塞而导致的性能问题。
4.4消息聚合
当有热门赛事时,同时在线可能达到千万级别,一条弹幕消息就要扩散到千万个终端,假如在线的每个人每秒发一条,需要发送消息量就是1kw*1kw,消息量非常大,此时消息分发层和接入层,压力都会很大。
分析发现:这些消息都是同一个房间的,属于热点房间,比如s赛房间,观众数量是无法减少的,那只能在消息数上做文章。业务消息推送不能减少,又要减少扩散的消息数,就想到了消息聚合。
针对房间消息,按照一定的规则进行消息聚合,批量推送:
消息聚合上线后,消息分发层对接入层调用QPS下降60%左右,极大的降低了接入层和消息分发层的压力。
4.5压缩算法
消息聚合后,降低了消息的数量,但是增加了消息体的大小,影响了写入IO,需要减少消息体大小,就想到了消息压缩。
压缩算法,选了市面上比较常用的两个:zlib和brotli,进行比较。
抓取了线上业务推送的数据,选择最高等级的压缩等级,进过压缩验证:
由此可见,brotli相比zlib有很大的优势,最后选择了brotli压缩算法。
选择在消息分发层进行消息压缩,避免在各接入节点多次重复压缩,浪费性能。上线后提升吞吐量的同时,也降低的宽带使用成本。
5、服务保障技术设计
现在有些业务是强依赖长连推送消息,消息丢失,轻则影响用户体验,重则阻塞业务后续流程,进而影响业务流水。针对长连服务消息保障,做了如下工作。
5.1多活部署
多活部署,通过在不同地理位置部署相同的系统架构和服务,实现了系统在单一地域故障时的快速故障转移,从而提高了系统的稳定性和可用性。
长连服务部署,主要做了以下几点:
- 1)长连接在国内华东、华南、华北地域均部署了接入点,支持三大运营商;华南和华中自建机房也部署了接入点;为支持海外用户,增加了新加坡机房独立接入点;
- 2)针对业务场景不同,在云上节点和自建节点之间,实时切换,因为云上节点和自建机房的成本是不一样的,在保证服务质量的前提下,尽可能的控制成本。
目前线上运行过程中,偶尔会遇到单节点或机房的网络抖动,通过控制层,对有问题的节点,进行秒级摘流,大大减少了对业务的影响。
5.2高低消息通道
多业务消息接入长连接,但不同消息之间的重要性是不一样的,比如弹幕消息和邀请pk消息,丢失几条弹幕对用户体验不会影响很大,但如果邀请pk消息丢失,则会导致pk业务无法进行后续的流程。
针对不同等级的消息,采用了高低优消息通道。重要消息走高优通道,普通消息走低优通道。这样重要和普通消息进行了物理隔离,消息分发优先保证重要消息。
针对高优通道,做了双投递的保障,在接入层做幂等去重。首先重要消息是针对用户级别的,量不会很大,所以对接入层的压力不会增加很大。另外双投递的job是部署在多机房的,这也就降低单机房网络抖动造成的影响。
高低优通道上线后,遇到过内网出网抖动,当时内网部属的job节点推送消息异常,而云上高优job节点可正常推送,很好的保障了高优消息的到达,进而保障了高优业务不受影响。
5.3高达功能
高低优通道解决的是job到接入层的这一个环节,但消息推送联路涉及到多个环节,比如服务层到job、接入层到客户端。
针对整个链路,通过实现必达机制来确保终端的到达率,简称高达功能。
功能实现:
- 1)每条消息引入msgID,客户端收到消息后进行幂等去重和ack回执;
- 2)服务端针对msgid进行ack检测,针对未ack的,有效期内再次重试下发。
最终到达率 = (1-(1-r)^(n+1)),其中:r为广播单次到达率,n为最大重试次数。
例如:r = 97%、n=2,那么最终到达率可以达到(1-(1-0.97)^(2+1)) = 99.9973%
6、进出”房“消息的送达保证设计
有些业务场景,需要用到用户进出房消息,比如用户A进入直播间,页面会显示欢迎用户A进入房间,或者是加入在线榜单。
1)进房消息会存在丢失,需要有补偿机制。想到可以通过连接心跳来补偿进房消息,但心跳是持续不断的,连接在线期间,业务希望只收到一次进房消息,所以进房消息需要有幂等机制。
2)出房消息也会存在丢失,如果丢失了,业务无法从在线榜单剔除用户,此时也需要有补偿机制。此时就需要增加连接的状态机,通过心跳维护状态机,当心跳丢失时,认为连接断开,用户退房。
7、未来规划
统一长连接服务经历数次迭代后,目前基本功能已经趋于稳定,后续对长连接服务进行改善和优化。
主要集中在以下几个方向:
- 1)数据化:进一步完善长连接全链路网络质量数据统计和高价值消息全链路追踪的能力;
- 2)智能化:端上建联、接入点选择等能够根据实际环境进行自动化调整;
- 3)性能优化:接入层的连接模块中,处理上下行消息的携程进行共享,减少接入层的携程数,进一步提升单机性能和连接数;
- 4)功能扩展:新增离线消息功能等。
8、参考资料
[3] 万字长文:手把手教你实现一套高效的IM长连接自适应心跳保活机制
[4] 用JWT技术解决IM系统Socket长连接的身份认证痛点
[5] TCP/IP详解 - 第11章·UDP:用户数据报协议
[6] TCP/IP详解 - 第17章·TCP:传输控制协议
[8] 快速理解TCP协议一篇就够
[9] 快速理解TCP和UDP的差异
[11] 到底什么是Socket?一文即懂!
[13] 假如你来设计TCP协议,会怎么做?
[15] 通俗易懂,高性能服务器到底是如何实现的
[16] 12306抢票带来的启示:看我如何用Go实现百万QPS的秒杀系统(含源码)