做直播后端这几年,我经常被问到一个问题:直播间右上角那个不断跳动的人气数字,到底是哪个接口返回的?实现逻辑是什么?尤其是当很多人想做直播数据监控、直播间热度分析的时候,第一反应都是去抓抖音的接口。我先把结论放在前面:人气数字不等于真实在线人数,接口也远没有你想的那么简单,而市面上流传的“协议”基本都游走在违规边缘。这篇文章我会从公开可观察的前端协议、通用实时通信技术、业务算法设计三个角度,完整拆解一个直播间人气系统是怎么运转的,也会讲讲哪些事情真的不能碰。
1. 先搞清楚“人气”到底是什么
1.1 一个数字背后的产品口径
你看到的“XXX人在观看”或者右上角的在线人数,产品上叫“直播间热度”或“实时观众数”。很多人误以为它就是当前有多少个WebSocket连接挂在这个直播间,实际上这个数字几乎是“算”出来的,而不是“数”出来的。
一个直播间的观众可以分成几类:正在看的人、刚进来的路人、刷到推荐页停留了3秒的人、点了关注但没进直播间的人。产品经理要的“人气”,通常是一个能够体现直播间活跃度和商业价值的综合指标。我做过电商直播相关的项目,我们当时的口径是:最近5分钟内有互动行为或持续观看超过一定时长的独立用户数,再叠加一定的热度权重。这个口径和“当前连接数”差别很大,因为用户退到后台、切到别的App、锁屏之后,客户端连接还在,但用户已经不看了。
还有一个容易被忽略的点:平台展示给人看的数字,普遍有“缩放”和“延迟”。抖音这类超大型直播平台,不会把真实在线人数原样展示,因为真实数值波动太剧烈,会给观众造成“人数怎么突然掉了1万”的负面感受。所以你会看到很多直播间的人数固定在某个区间,或者变化非常平滑,这背后是有意做的平滑策略。
1.2 为什么人气不直接等于在线人数
如果你自己做过带聊天室功能的应用,第一反应肯定是:用一个Redis计数器,有人进房间就incr,有人离开就decr,然后前端定时拉一下。这套方案在几百人、几千人的场景下完全够用,但放到百万级同时在线的直播间,会有三个致命问题。
第一,连接数不等于真人。直播间有大量的协议连接是客户端主动建立的,但用户可能只是把App挂在后台听声音,或者手机锁屏了。这个状态下如果计入在线人数,数字会严重虚高。第二,短时间进出非常频繁。一场直播的流量高峰里,每秒可能有上千人进出,如果每次进出都更新一次精确计数,存储层和推送层都会被打爆。第三,产品需要的是“可解释的稳定指标”,不是一个物理计数值。平台要拿这个数字投放广告、和主播结算、做流量调控,它必须是可干预、可校准的。
这时候你会明白,真正的人气算法通常分成两层:底层是实时采集所有用户行为事件,例如进房、退房、评论、点赞、送礼、观看时长;上层是消费这些事件流,做滑动窗口计算、去重、加权,最终产出一个供展示用的热度值。
1.3 从后端视角定义数据链路
我习惯把整条链路拆成这样几段:
- 客户端行为采集:进房、退房、心跳、评论、点赞、送礼、分享,全部以消息事件的形式上报。
- 接入层:WebSocket接入,负责维持长连接、鉴权、心跳管理。
- 消息管道:事件进入Kafka或者类似的分布式消息队列,按直播间ID做分区,保证同一个直播间的消息是有序的。
- 实时计算层:消费消息流,做5分钟或1分钟的滑动窗口计算,产出当前人气值。
- 下发通道:把人气值、榜单、评论等数据推回客户端,通常还是走WebSocket。
- 客户端渲染:拿到增量数据后做动画插值,让数字平滑滚动。
很多研究“抖音协议”的人只看到最后一层,也就是客户端接收到的那几条消息,于是以为拿到消息格式就等于拿到了协议。实际上真正复杂的是中间那一整条流水线。就算你拿到了消息字段,没有签名、鉴权和风控策略,照样连不上服务器。
2. 客户端是怎么拿到人气数据的:协议侧拆解
2.1 最常见的三种实时链路
做实时数据下发,业界通用的方案就三种:HTTP短轮询、WebSocket长连接、SSE单向推送。直播间这类高实时、双向交互的场景,主流方案是WebSocket,但并不是所有数据都走同一条通道。
我观察过不少直播类App的实际表现,包括我自己参与过的项目,通常会有两条通道并存。一条是HTTP短连接接口,负责基础数据的拉取,例如进入直播间时先请求一次当前房间的基础信息,包括主播信息、房间状态、初始人气值、推荐位列表。另一条是WebSocket长连接,负责后续所有增量数据推送,比如有人进入的提示条、人气数字变更、评论消息、礼物特效事件。
为什么要分两条通道?因为HTTP请求天然适合“一次性拉取全量状态”的场景,而WebSocket适合“持续接收增量事件”。你要是把所有数据都塞进长连接,客户端冷启动时会有一大段空白期,体验很差。所以常规做法是:先用HTTP把快照拿到,再用长连接做增量同步。
2.2 轮询和长连接的选择逻辑
很多人好奇为什么不用更简单的定时轮询。早期直播平台确实用过,大概2秒到5秒拉一次在线人数。这个方案的缺点是:人数少的时候浪费请求,人数多的时候数据还是不够实时,同时服务器压力巨大。
后来演进成WebSocket,原因不只是实时性,还有一个经济学考量:HTTP轮询每个请求都要走完整的TCP握手、TLS握手、HTTP头解析,即使开启keep-alive,也要处理大量闲置连接。而WebSocket只需要一次握手,后续都通过同一个TCP连接传输帧数据,非常适合直播这类高频率消息场景。
推送通道还有一个设计细节:服务端不会把每一次人气变化都单独推一条消息,那样消息量太大了。一般会做批量聚合,服务端每1到2秒算一次最新值,然后通过一条消息发出去,消息里可能包含人气值、在线人数、互动次数等多个字段。这就是为什么你在Wireshark里看直播App的流量,WebSocket帧每隔一两秒才出现一条,而不是每毫秒都在跳。
2.3 消息格式与增量更新
直播间消息格式,各个平台大同小异,基本逃不出几个套路:一个命令字(cmd或type),一个数据体(data),一个序列号(seq),有时还带一个时间戳。人气更新这类消息的data体里通常有当前人气值、在线人数、涨粉数、点赞数等。
增量更新是这个链路里比较讲究的部分。一个直播间的人气数字如果每次都推送全量值,客户端倒是简单,但服务端要承担很大的序列化开销。更重要的是,如果用户断线重连,推送的是全量值还是增量值,决定了对账逻辑的复杂度。
我建议的做法是:服务端推送增量事件,同时保留全量快照接口。客户端正常在线时只处理增量,断线重连后先拉一次快照,再恢复增量推送。这样做的好处是,重连期间错过的消息不会导致客户端状态永久错位。
增量事件通常携带一个单调递增的seq,客户端可以拿它做乱序判断和去重。你如果自己设计这套协议,seq一定不要用时间戳,因为同一毫秒内可能有多条消息,时间戳无法保证顺序。
2.4 前端如何让右上角数字滚动
协议层拿到人气值后,前端还有一个让人忽略的工作:数字不能一下子跳变。你看足球比赛的观赛人数,往往是平滑地往上滚,很少突然掉一半,因为有动画插值。
前端编程里,拿到新值不直接setState,而是把新旧值之间的差值分摊到几百毫秒内,用requestAnimationFrame逐帧更新。这样用户看到的是一段自然滚动,而不是生硬的数字跳变。
中间还涉及防抖。如果服务端一秒内推来3个人气变更消息,前端可能只取最后一条做动画,中间的过渡过程靠自己补间。这个策略反过来也会影响你对服务端协议的判断——你抓包看到手机上人气数字在滚动,不代表服务端每秒推了那么多次,很可能是前端自己在补动画。
3. 人气数字是怎么被算出来的:算法与风控
3.1 核心指标:实时热度、并发UV、平均停留
说完协议侧,再看服务端的算法。我参与过直播业务后端的项目,可以说,人气值算法没有一个统一标准,但核心指标基本围绕三个维度:实时热度、并发UV、平均停留时长。
实时热度是一个综合分,不仅看当前在线人数,还要看单位时间内的互动量。评论区刷得飞快的直播间,热度一定比人数相同但没人说话的直播间高。并发UV指的是单位时间内的独立用户数,这个指标能反映直播间拉新能力。平均停留时长则是反映内容质量的关键,停留时间越长,说明内容越吸引人。
这三个指标会按不同的权重合成一个分数,再经过归一化处理,映射到展示给人看的数字区间。权重并不是固定的,平台会按直播类目、时间段、主播等级动态调整。比如游戏直播更看重弹幕互动,颜值直播更看重在线时长,带货直播更看重点击购物车的转化行为。
3.2 加权与校准
如果只是把在线人数直接展示,绝大多数直播间的数字会显得冷冰冰的,产品团队通常会给数字加一个“温度系数”。举个实际例子,某直播间真实并发UV是1万,但平台展示出来是1.3万,多出来的部分就是通过互动行为加权的热度增量。
加权方案里有一个常用模型:基础权重加行为加成。每个进入直播间的用户计1个基础人气,产生评论加若干人气,点赞、送礼、分享分别再加不同权重。这个模型的好处是能引导主播去引导互动,坏处是容易被刷量,所以必须有风控。
校准这个动作也很关键。由于展示值经过缩放和加权,它和后台真实数据不是完全对应的。业务方如果要拿真实数据做结算、投放、推荐,不能直接读展示值,必须读内部原始指标。我在项目里就遇到过运营同学拿着前端展示的数字来核对日报,结果对不上,折腾了半天才发现两边口径不同。
3.3 降噪:去重、异常流量、账号质量
人气算法的另一半是降噪。直播间的流量里存在大量异常数据,典型的包括:
- 同一设备短时间反复进出,产生大量进房事件。
- 机器人账号或批量注册账号挂机,维持虚假在线时长。
- 通过改机工具模拟大量设备在线。
- 黑灰产团伙用脚本批量点赞、评论。
实时计算层要做的是在窗口内对这些行为做识别和剔除。常见的做法是维护一个设备指纹和账号维度的小表,记录行为频次。比如一个账号一分钟内进房超过20次,或者一个设备指纹关联的账号数超过阈值,就会被标记为低质流量,不计入人气。
去重不是只做一次,而是多层。接入层做基础频率限制,消息管道做设备维度的滑窗去重,实时计算层再做账号质量过滤。每一层都有延迟,所以最终产出的人气值往往不是实时的,而是延迟几十秒甚至几分钟的“准实时值”。这也是为什么你看到的在线人数总感觉有点滞后。
3.4 分片与最终一致性
超大直播间的消息量会达到每秒数万条甚至更高。这时候单机计算必然扛不住,需要做分片。分片键一般是直播间ID,同一个直播间的所有消息进入同一个计算任务。这个任务内部管理一个滑动窗口,窗口大小通常是5分钟,步长1秒。
分片带来一个新的问题:同一个直播间的不同事件可能落到不同的消息分区,如果分区策略不当,顺序就乱了。通常的做法是消息队列按直播间ID做key,保证同一个直播间的事件进入同一个分区,这样消费的时候不会乱序。但乱序依然可能发生在客户端网络层,所以协议里seq还是必须有。
计算完成之后,人气值会写入Redis或者内存态存储,再通过推送服务推给客户端。多个计算节点同时产出数据时,可能会短暂出现不同客户端看到的人气值不一样的情况。这就是最终一致性在实时系统里的体现,几分钟内会收敛,但做不到强一致。如果谁能做到全球所有用户看到的在线人数完全一致,那他一定是牺牲了实时性。
4. 想自己动手验证?合规实验指南
4.1 三条合规路线
我知道很多人看到这个话题,第一反应是想自己抓包、逆向、写脚本。我必须把话说清楚:直接分析抖音的私有协议、逆向客户端签名、绕过风控,属于违反平台规则甚至法律的行为,我不提供这方面的帮助,也不建议任何人去做。如果你确实对直播协议和算法感兴趣,有合规的路线可以走:
- 阅读平台开放的API文档。抖音开放平台、微信的公众平台、电商直播的开放接口,都有合法的数据接入方式,能拿到部分公开数据和授权数据。
- 自己做一套直播间服务端。用Netty或Go写一个支持WebSocket的直播间后端,自己定义协议,自己写人气算法,这能让你把整条链路跑通。
- 研究通用协议标准。WebSocket是RFC 6455标准的公开协议,你可以抓自己服务器的包,研究消息帧格式、心跳机制、关闭帧,这些知识完全可以迁移到任何直播系统上。
这三条路线都不碰红线,又能真正学到东西。我认识的很多优秀后端工程师,都是从自己写一个聊天室、一个直播间开始理解这套体系的。
4.2 一个可复现的WebSocket订阅示例
如果你想亲手体验“长连接推送”的感觉,不需要逆向任何大厂App,用公开的WebSocket服务就能试。很多免费公共服务支持你直接连接,然后订阅一个频道,比如一些公开的加密行情频道、公共消息广播频道。
我提供一个最小示例,用Python实现一个WebSocket客户端,先连接服务器,然后订阅一个主题,并持续接收消息。这段代码用的是标准websockets库,只做通用协议演示,不涉及任何私有协议。
import asyncio import json import websockets async def subscribe(): uri = "wss://your-public-websocket-service.example/ws" async with websockets.connect(uri) as websocket: # 连接成功后发送订阅消息 subscribe_cmd = { "cmd": "subscribe", "topic": "room_popularity_demo", "seq": 1 } await websocket.send(json.dumps(subscribe_cmd)) print("已发送订阅消息") # 持续接收服务端推送 async for raw_message in websocket: message = json.loads(raw_message) if message.get("topic") == "room_popularity_demo": popularity = message.get("data", {}).get("popularity") print(f"最新人气值: {popularity}") # 收到一定数量后主动退出,方便测试 # 这里只是演示,实际场景会一直循环接收 asyncio.run(subscribe())这段代码的核心逻辑很简单:建立连接,发送一条订阅消息,然后进入接收循环。真实的直播间客户端逻辑其实也是这样,只是连接鉴权、签名、心跳、业务字段复杂很多。
4.3 重连、心跳与消息幂等
自己写客户端时会遇到三个经典问题,这三个问题在真实生产环境里一个都躲不掉。
第一个是心跳。WebSocket连接长时间没有数据传输,中间网络设备可能会回收连接,所以客户端必须定时发送心跳包,典型的间隔是30秒到60秒。心跳包可以是ping帧,也可以是自定义的业务心跳消息。服务器的职责是维护一个超时时间,超过时间没收到心跳就主动断开。
第二个是重连。长连接断线是常态,客户端要准备指数退避重连,从1秒开始,翻倍到2秒、4秒、8秒,上限通常设在30秒或60秒。如果断线是因为服务端发布重启,重连还要带一个随机的抖动,防止所有客户端同时重连,把服务器打崩。
第三个是消息幂等。网络重传、重连恢复都可能让你收到重复消息。客户端处理消息时要么带上seq去重,要么保证处理逻辑本身是幂等的。比如收到人气更新,直接覆盖当前值就是幂等的,而如果是累加操作,重复收到就会出问题。设计协议时就要分清哪些消息是幂等的,哪些不是。
4.4 不建议碰的东西和红线
这个部分我写得很直白:不要去尝试破解客户端的签名算法,不要尝试模拟客户端的加密握手,不要批量注册账号去拉所谓的人气接口,不要做刷量外挂,更不要把这些技术用在外挂和灰产上。这类行为轻则封号,重则承担法律责任。
做技术研究时,有一个判断红线的方法:如果这个操作需要主动规避平台的安全机制才能完成,那它就不是技术问题了。真正的协议研究应该是基于公开文档、开放接口和自己架设的服务,而不是踩在别人的生产系统上做实验。
我自己在带团队时也反复强调,看抖音的技术分享、学习他们的架构思想完全没问题,但别动“逆向私有协议”的念头。写代码之前先想清楚合法性,这是高级工程师和脚本小子的根本区别。
5. 实操中的坑位记录
5.1 线上消息乱序
我在自研直播间推送系统时,踩过最深的坑就是消息乱序。最开始消息全部进一个Kafka主题,按时间戳排序,表面看起来没问题,但高峰期一上来,消息在多个消费实例之间分发,顺序就乱了。用户会看到一种诡异的现象:有人进入直播间的提示条出现得比离开提示条还晚,人气数字倒着跳。
排查到最后发现,问题出在分区策略上。Kafka只保证单个分区内的顺序,如果消息没有按直播间ID做分区key,顺序就无法保证。当时的修复方案是消息producer端强制按roomId路由,consumer端单线程消费同一个roomId的消息。这个教训我现在还一直用:任何要求顺序的实时功能,第一件事就是确认消息队列的分区策略。
乱序问题还延伸出另一个设计原则:协议字段里凡是涉及顺序的,都不要依赖时间戳,因为客户端时钟和服务端时钟本来就不同步。我用单调递增的seq解决,客户端缓存上一个seq,发现新消息的seq比缓存小,直接丢弃或者重新请求快照。
5.2 客户端刷新频率陷阱
做前端展示时,我也吃过刷新频率的亏。后端一开始推人气值的频率是每秒一次,我们觉得挺合理,结果压测时发现某个大直播间有几十万在线,每秒推一次全量消息给所有客户端,推送通道的带宽开销非常吓人。
后来做了两件事。第一,服务端按房间聚合推送,1分钟内的人气变化合并成几条消息下发,而不是每次都推。第二,客户端加入节流,就算服务端每秒都推了,渲染层最多500毫秒更新一次UI,多余的变更只作为最终值的参考。
这里有个技术细节:服务端减少推送频率,不代表用户看到的人气数字就更卡,因为客户端可以做动画插值。你完全可以每2秒推一次,客户端通过缓动函数把数字滚动的过程铺满这2秒,视觉上很流畅。所以协议设计的核心,不只是降低延迟,还要控制消息量和客户端渲染开销。
5.3 字段变化的灰度期
有一次我们调整了消息体里的字段定义,把原先的online_num改成了popularity,客户端和对应的解析逻辑也都改了。但发布之后发现部分直播间的人气数字全部变成0,查了半天才发现是灰度发布导致的字段不匹配——有些旧客户端还在用旧字段名解析,服务端却已经开始推新字段。
这件事之后我定了一个规矩:涉及协议字段的变更,必须做兼容设计。新老字段并行推送至少一个版本周期,客户端先兼容读取新字段,等旧客户端占比低于阈值再删除旧字段。哪怕是小字段的改名,也要当作一次完整的协议变更来对待。
真实生产环境里,协议不是你改完就结束的,而是要等到所有客户端都升级完才算结束。灰度期间最容易出现的问题就是客户端和服务端版本不匹配,所以协议版本号字段最好一开始就设计好,不要等出了事故再补。
5.4 和后端对账的思路
最后分享一个对账经验。直播间的展示人气值,和后台统计报表里的数据经常对不上,运营每次都来问是不是系统出bug了。绝大多数时候都不是bug,而是口径不同。
展示值经过加权、缩放、平滑和延迟处理,后台报表里的数据则是全量的、去重后的、无缩放的真实指标。对账时不能直接把两个数字放一起比,要先对齐时间窗口、用户口径、去重规则和缩放比例。
我给团队的建议是,所有经过加工后下发给前端的数字,都要保留一份原始终端埋点日志,记录下发值、下发时间、对应的事件流窗口。这样一旦运营质疑数据,就能快速定位是哪一层加工导致的偏差,是权重问题还是风控触发,而不是互相甩锅。
踩过几次坑之后我的体会是,这类实时系统的难点不在某一个单一技术上,而在链路太长,每一环的偏差都会被放大。做协议也好,做算法也好,先把数据链路的对账机制想清楚,能省掉后面90%的排查时间。如果你也打算自研直播间的实时数据系统,我的建议是先把WebSocket的标准流程吃透,再考虑复杂的算法,基础协议这块稳了,后面才有得谈。