1、引言
一套完整的直播系统核心功能有两个:
1)实时音视频的推拉流;
2)直播间消息流的收发(包括聊天消息、弹幕、指令等)。
本文主要分享的是百度直播的消息系统的架构设计实践和演进过程。
实际上:直播间内用户的聊天互动,虽然形式上是常见的IM聊天消息流,但直播消息流不仅仅是用户聊天。
除用户聊天外:直播间内常见的用户送礼物、进场、点赞、去购买、主播推荐商品、申请连麦等互动行为的实时提醒,也是通过消息流下发的。
此外:直播间关闭、直播流切换等特殊场景,也依赖消息流的实时下发。
所以,直播系统内的消息流可以认为是直播间内主播与用户间实时互动和直播间实时控制的基础能力,也是系统支撑。如果说实时音视频推拉流是直播系统的灵魂,那消息流可以说是直播系统的骨架,它的重要性不言而喻。
那么,如何构建直播的消息系统,又有哪些挑战需要解决,借着本文我们一起来梳理一下。

(本文同步发布于:http://www.52im.net/thread-3515-1-1.html)
2、系列文章
本文是系列文章中的第4篇:
《直播系统聊天技术(一):百万在线的美拍直播弹幕系统的实时推送技术实践之路》
《直播系统聊天技术(二):阿里电商IM消息平台,在群聊、直播场景下的技术实践》
《直播系统聊天技术(三):微信直播聊天室单房间1500万在线的消息架构演进之路》
《直播系统聊天技术(四):百度直播的海量用户实时消息系统架构演进实践》(* 本文)
3、与普通IM群聊的区别
直播间内的聊天消息,经常被类比于普通的IM群聊功能。
群聊是大家比较熟悉的即时通讯(IM)场景,直播间内聊天和群聊,二者有相似性,但也有本质的区别。
对比二者的特点,直播消息与IM群聊主要有以下区别:
1)参与人数不同:IM群聊的参与人数上千人就是很大的群了。但对于高热度的大型直播场景,例如国庆、阅兵、春晚等,单直播间累计用户是百万甚至千万量级的集合,同时在线人数可达数百万人。
2)组织关系不同:IM用户进群退群,是相对低频的操作,用户集合相对固定,用户进出的变更频度不会特别高。而用户进出直播间,是非常频繁的,高热度直播的单直播间每秒面临上万用户的进出变更。
3)持续时间不同:IM群聊建立后,聊天持续时间可能比较长,几天到数月都有。而直播间大部分持续不超过几个小时。
4、核心技术挑战
根据上节中,直播消息和IM通聊的类比分析,针对直播中的消息系统,我们可以提炼出两个核心技术挑战。
挑战一:直播间内用户的维护
1)单直播间每秒上万用户的进出变更(实际进入直播间峰值不超过2万QPS,退出也不超过2万QPS);
2)单直播间同时数百万用户在线;
3)单直播间累计用户达千万量级。
支持在线百万、累积千万两个集合,每秒4万QPS更新,有一定压力,但有支持高读写性能的存储应该可以解决,例如redis。
挑战二:百万在线用户的消息下发
面对百万在线用户,上下行都有大量的消息,从直播用户端视角分析:
1)消息的实时性:如果消息服务端做简单消峰处理,峰值消息的堆积,会造成整体消息延时增大,且延时可能产生很大的累积效应,消息与直播视频流在时间线上产生很大的偏差,影响用户观看直播时互动的实时性;
2)端体验和性能:端展示各类用户聊天和系统消息,一般一屏不超过10-20条。如果每秒有超过20条的消息下发,端上展示的消息基本会持续刷屏。再考虑到有礼物消息的特效等,大量的消息,对端的处理和展示,带来持续高负荷。所以,对于一个长时间观看直播的用户端来说,如果出现持续的大量消息,端的消息消费会有显著的性能压力,且过多消息会有累积效应。
由于技术挑战一不难解决,以下内容主要讨论技术挑战二。
5、技术设计目标
综合考虑上节的技术挑战和直播业务场景,我们对于消息系统的需求目标有比较明显的指标定义。
技术设计目标定义大致如下:
1)实时性方面:端和端的消息要达到秒级;
2)性能方面:消息服务能支持同一直播间内百万以上用户同时在线下发;
3)峰值处理:对于峰值时的过多消息,丢弃是合理适当的处理方式;
4)基于合理的端用户体验,单直播间内每秒消息数假设不超过N条。
现在:问题的核心是,如何做到把不超过N条的消息,在S秒内,下发到直播间内的百万用户(假设 N<=20,S<=2)。
6、从普通IM群聊的技术实现上找灵感
6.1 普通IM群聊消息收发分析
IM群聊数据流及压力点:

如上图所示,首先具体分析一下普通群聊的消息收发流程:
1)对于群group-1,分配一个群公共消息信箱group-mbox-1;
2)群group-1内的用户user-1,由手机端APP-1上发出消息msg-1;
3)服务端接收到消息msg-1,检查user-1是否有权限,如有权限,将msg-1存储到群信箱group-mbox-1,生成相应msgID-1;
4)服务端查询group-1对应的用户列表groupUserList-1;
5)基于groupUserList-1拆分出所有独立群用户:user-1、user-2 ... user-n;
6)对于每一个用户user-i来说,需要查询用户user-i的所在设备device-i-1、device-i-2、device-i-m(因为一个账号可能登录多个设备);
7)对于每个设备device-i-j来说,长连接通道都会建立一个独立的长连接connect-j以服务于该设备;但由于connect-j是由端上APP-1连接到长连接服务的,具有动态性,所以,查询device-i-j与connect-j的对应关系时,需要依赖一个路由服务route来完成查询;
8)在查得connect-j后,可以通过connect-j下发msg-1的通知groupmsg-notify-1;
9)如果用户user-i正在使用device-i-j的手机端APP-1,用户user-i就可以立即从长连接connect-j上收到msg-1的通知groupmsg-notify-1;
10)在接收到groupmsg-notify-1后,手机端APP-1中的消息SDK根据端本地历史消息记录的最后一条消息latestMsg对应的消息ID即latestMsgID,来向服务端发起拉消息请求fetchMsg,拉取group-1中从latestMsgID+1到最新的所有消息;
11)服务端收到拉消息请求fetchMsg后,从group-mbox-1中取出latestMsgID+1到最新的所有消息,返回给端;如果消息过多,可能需要端分页拉取;
12)端APP-1拉取到group-1中从latestMsgID+1到最新的所有消息,可以做展示;在用户在会话中阅读后,需要设置所有新消息的已读状态或者会话已读状态。
6.2 普通IM群聊主要压力
如果完全重用普通群聊消息的下发通知到端拉取的全过程,对于user-1发的一条消息msg-1,如果需要支持一个实时百万量级的群消息,大概有以下几个每秒百万量级的挑战。
首先:秒级拆分出用户列表groupUserList-1,需要秒级读出百万的用户列表数据,对于存储和服务是第一个百万级挑战。
第二:对于拆分出群中的所有独立用户user-i,需要秒级查询出百万量级的devi