直播和点播最根本的差异是:内容正在产生,而且产生的过程随时会中断。这带来一串点播完全没有的问题。
第一版是照着「能推能拉就行」做的,四个问题很快暴露。
一是推流地址被盗推。推流地址是固定的字符串,抓包或者主播不小心泄露之后,别人拿这个地址往你的房间推流,观众看到的是完全无关的内容。而且我们无法区分谁在推——地址对了就收。这在内容安全上是很严重的问题。
二是主播断流就直接结束直播。主播过隧道、进电梯、切换 WiFi,几秒钟的断流让我们判定「主播下播了」,房间关闭、几千观众被踢出去,主播恢复后要重新开播、观众全散了。主播的反馈是「你们的直播老是自己断」。
三是所有房间都开多档转码,成本失控。同时在播几千个房间,绝大多数只有几个观众甚至没人看。给这些房间也出三档清晰度,转码集群的资源全浪费在没人看的内容上。
四是房间状态在并发下错乱。「主播主动下播」和「断流超时自动结束」可能同时发生,两个流程都去改房间状态,结果出现了「已结束的房间又变回直播中」这种状态,回放生成也跑了两次。
所以四个设计:推流地址签名加媒体服务器回调二次鉴权、断流进容忍窗口而不是立即结束、转码按热度分档、房间状态用状态机条件更新。
自建媒体服务器还是用云直播。云直播(厂商提供推流拉流转码 CDN 全套)接入快、运维省,但成本按流量计费,量大之后很贵,而且定制受限(比如要在流里插审核标记、要自定义转码参数)。自建可控但要维护媒体服务器集群、处理协议细节、自己做监控。我们的选择是混合:主要流量走自建,突发和边缘地区溢出到云服务。判断依据是「流量规模」和「定制需求强度」——如果只是几十个房间,直接用云服务,自建是过度设计。
直播延迟和流畅度是直接冲突的。低延迟协议(WebRTC 之类)延迟能到几百毫秒,但抗弱网能力差、CDN 支持不如 HTTP 系协议成熟、成本高;HTTP 系协议(HLS 之类)延迟几秒到十几秒,但极其稳定且 CDN 分发成本低。我们的做法是按场景分:普通直播(观众只看不互动)走 HTTP 系协议,接受几秒延迟换稳定和低成本;需要强互动的场景(连麦、答题、抢购)才用低延迟协议。「按互动强度选协议」这个判断比一味追求低延迟正确。
踩过的坑:断流容忍窗口内主播重连,但推流被自己的旧连接挡住了。主播网络抖动,旧的推流连接在媒体服务器侧还没被清理(TCP 层还没超时),主播重连时回调鉴权发现「该房间已有推流」,于是拒绝了他。主播反复重连都失败,最后只能等旧连接超时。修法是回调鉴权时如果发现已有推流,要判断那路推流是否还在实际收到数据(有没有心跳、最近有没有数据帧),僵死的旧连接要主动踢掉让新连接进来。教训是:「同一房间只允许一路推流」这个限制必须配合「僵死连接检测」,否则会挡住合法的重连。
踩过的坑二:并发的下播导致回放生成两次、礼物结算两次。主播点了下播,同时断流超时也触发了自动结束,两个流程都往下走,回放生成了两份,礼物结算跑了两遍(幸好有幂等表挡住了资金部分,但回放确实生成了两份占存储)。修法是房间状态用条件更新做唯一入口——只有把状态成功改为「已结束」的那个流程才继续执行收尾动作,另一个影响行数为 0 直接返回。教训是:多个触发源的收尾流程,必须用一个状态变更作为「谁有权执行收尾」的裁决点。
没做的部分:没做转码任务的优先级调度。头部房间的转码应该优先于长尾房间,我们是按先到先转。活动期间转码集群紧张时,这个缺陷会让头部房间的多档也上不去。
开播到首帧可见:从「主播端开始推流」到「观众端渲染出第一帧」的端到端时间。这个链路很长(推流建连、媒体服务器接流、转封装、CDN 回源、观众端拉流解码),所以要分段打点才能说清瓶颈在哪。P95 约 1.8s。要说明用的是什么协议——HTTP 系协议的首帧本身就比低延迟协议慢(要等一个切片),不说协议这个数字没有可比性。
「误结束未再出现」怎么验证:构造场景——主播端主动断开推流若干秒后恢复,验证房间未结束、观众连接保留、弹幕通道保留、恢复后状态回到直播中。还要测容忍窗口超时的分支:断流后不恢复,验证窗口超时后正常结束并生成回放。以及那个坑:断流后立刻重连,验证不会被自己的僵死旧连接挡住。
转码资源下降:对比「全量房间开多档」和「按热度分档」两种策略下的转码集群占用。用「明显下降」这种定性描述而不是精确百分比——因为它完全取决于房间的观众数分布(长尾房间占比越高降幅越大),报一个固定百分比会被追问穿。可以报的是「同时在播房间中实际开启多档转码的比例」,这个口径清晰。
不要报「直播成功率 99.9%」。直播中断的主要原因是主播的网络和设备,平台改不了。可以报的是「因平台原因导致的直播中断次数」——绝对数,口径清晰,而且是我们能控制的。
这个模块最重要的认知是:同一个直播间里,弹幕和礼物的可靠性要求相差一个量级,必须用完全不同的策略。第一版把它们当成同一类「互动消息」处理,两头都出问题。
一是弹幕逐条推送导致扇出爆炸。一万人的房间,一条弹幕要推一万次。热门房间每秒几千条弹幕,扇出就是每秒几千万次推送,长连接网关直接被打满,连礼物消息也推不出去了。
二是弹幕全量下推客户端也显示不了。每秒几千条弹幕,屏幕上根本放不下,客户端收到也是丢掉。我们花巨大成本推过去的东西,客户端直接扔了。
三是礼物用了和弹幕一样的「尽力投递」,出了资损。用户送了礼物,扣了钱,但推送消息丢了,主播和其他观众没看到礼物特效,用户来投诉说钱扣了礼物没到。反过来也出过重复扣款——客户端超时重试,服务端处理了两次。
四是打赏榜单实时算不动。榜单要按本场累计打赏金额排序,每次有礼物就重新聚合,热门房间下这个查询扛不住。
所以四个设计:弹幕按窗口合并下推、超热房间抽样并明确弹幕可丢、礼物走幂等加余额条件扣减且必达、榜单用 ZSet 增量维护。
where balance >= 金额,影响行数为 0 就是余额不足。绝不能先查余额再扣——中间的窗口就是并发漏洞,用户快速连送能扣成负数。ZINCRBY,读榜单直接取前 N。只保留 Top N——几百万人的房间不需要全量排名。弹幕合并带来几百毫秒延迟,这个代价可以接受。弹幕不需要毫秒级实时——观众看到的弹幕晚 300ms 完全无感。但连麦或答题场景下的消息不能走这个合并通道(那些需要低延迟),所以合并只用于弹幕这类「氛围型」消息。按消息的实时性要求分通道,而不是一套策略打天下。
弹幕抽样对被抽掉的用户是不公平的,这个要正面承认。普通用户在超热房间里发的弹幕可能永远显示不出来。缓解手段是本地回显(他自己能看到),以及抽样只在超过显示上限时才触发(大部分房间压根不抽样)。但本质上这是「付费优先」的产品选择,不是技术最优——说清楚这是业务决策而非技术限制,比假装公平好。
踩过的坑:礼物特效已经播了,但扣款失败。第一版为了体验做了乐观更新——客户端点赠送后立刻播特效再发请求,结果余额不足或网络失败时特效已经播完了,用户以为送出去了,主播也看到了,实际没扣钱没入账。主播来问「刚才那个礼物怎么没算」。修法是礼物绝不乐观更新,必须等服务端确认扣款成功才播特效。这和骑手端抢单的判断一致——涉及资金或竞争结果的操作不能乐观。体验上的补偿是把扣款接口做得足够快(P95 35ms,用户感知不到等待)。
踩过的坑二:弹幕洪峰把礼物消息挤掉了。弹幕和礼物共用一个消息队列和推送线程池,热门房间弹幕暴涨时队列积压,礼物消息排在几万条弹幕后面,几十秒才推到房间,用户送了大额礼物半天没动静,投诉。修法是队列和线程池按消息优先级隔离。教训是:「分级可靠性」如果不在资源层面隔离,就只是纸面上的分级——同一个队列里,低优先级消息一定会挤占高优先级消息。
没做的部分:没做弹幕的历史回看(回放时同步显示当时的弹幕)。这需要把弹幕按时间轴存下来并在回放时对齐播放,存储和对齐都有成本,而弹幕本身是「允许丢失」的数据,两个定位有冲突,当时没做。
弹幕合并的效果:压测单房间 5000 条/秒弹幕、房间内一万个连接,对比「逐条推送」和「窗口合并」两种模式下网关的实际推送次数与出口带宽。合并后推送次数从「与弹幕量持平」降到「每秒数次乘以连接数」。要说明窗口长度,因为推送次数直接由它决定。
礼物接口 P95:压测时要构造余额扣减的热点——同一用户快速连送(考验幂等和条件更新)、以及大量不同用户同时送(考验整体吞吐)。两种都要测,P95 约 35ms。
重复赠送与余额超扣验证:限速模拟弱网,同一用户对同一礼物连点 5 次,检查礼物流水只有一条、余额只扣一次;再用高并发送礼物直到余额耗尽,检查余额不会被扣成负数。这两个是资金正确性,必须用确定性场景验证而不是比率。
优先级隔离验证:在弹幕洪峰(5000 条/秒)的同时发送礼物,量礼物消息从服务端到客户端的延迟是否仍在正常范围。这是验证「分级可靠性真的落地了」的关键测试,也正是我们踩过坑的地方。
不要报「弹幕到达率」。弹幕的契约本来就是允许丢失,报到达率自相矛盾。可以报的是「抽样触发的房间比例」和「合并后的推送次数」。也不要报「礼物零丢失」——正确表述是「流水落库后必可对账,投递失败有重试与进房补齐」。
update account set balance = balance - ? where user_id = ? and balance >= ?,影响行数为 0 就是余额不足,明确返回。绝对不能先查余额再扣——查和扣之间的窗口就是并发漏洞,用户快速连送能扣成负数,这是压测里很容易压出来的。另外礼物流水必须落库,因为余额是当前状态会被覆盖,流水是不可变的事实记录,主播分成和用户账单都靠它算。还有个体验上的坑我们踩过:第一版为了流畅做了乐观更新,点赠送后立刻播特效再发请求,结果扣款失败时特效已经播完,用户以为送出去了、主播也看到了,实际没扣钱没入账。修法是礼物绝不乐观更新,必须等服务端确认才播特效——涉及资金或竞争结果的操作不能乐观。
ZINCRBY 增加该用户在本场的累计金额,读榜单直接 ZREVRANGE 取前 N,都是对数级复杂度。如果每次都去聚合礼物流水表,热门房间下这个查询扛不住(一场直播几十万条流水)。三个关键细节:榜单只保留 Top N(几百万人的房间不需要全量排名,超出 Top N 的用户看自己的排名可以单独算或者只显示「未上榜」);ZINCRBY 要在礼物流水落库成功之后做,顺序反了会出现「榜单有但流水没有」的对不上;下播后落库归档并清理 Redis 数据,否则同时在播几千个房间的榜单数据会一直累积。另外榜单的准确性可以最终一致——如果 Redis 数据丢失,可以从礼物流水表重算恢复,这也是为什么流水必须落库。
这个模块要先讲清直播审核和点播审核的根本差异,这也是它最值得讲的地方。
点播可以「先审后发」——内容是已经存在的文件,审完再放出来,最坏情况是发布慢几分钟。直播不行,内容正在产生,观众正在看,你没有「先审」的机会。所以直播审核的目标不是「拦住违规内容」(做不到),而是「把违规内容的暴露时间压到最短,并且留下证据」。这个定位说不清楚,整个方案的取舍都会跑偏。
第一版的问题很直接。
一是录制到最后才落盘,异常中断就全丢。录制是一个大文件,直播结束时才写完。主播中途断流或者媒体服务器进程异常,整场录制什么都不剩,主播想要回放也没有。
二是审核只在下播后跑,等于没有审核。违规内容已经播完了、被几万人看过了,事后审出来只能封号,而伤害已经造成。
三是切断推流没有证据。切断后主播申诉「我什么都没做」,我们只有一条「疑似违规」的记录,拿不出具体是哪一帧哪一句话,没法定论。
四是处置只有「切断」一档。轻微的疑似违规也直接切断,误伤主播很严重(他的整场直播和当天收入都没了),而放过又有风险。
所以四个设计:分段录制、实时抽帧与音频转写送审、命中高危即时切断并留存证据、按风险分级处置而不是一刀切断。
直播审核的目标必须重新定义:不是「拦住」而是「压缩暴露时间并留证据」。内容正在产生,物理上不存在「先审后发」的可能。把目标定成「零违规内容流出」是不现实的,会导致所有取舍都朝「宁可误杀」倾斜,最后误伤大量正常主播。正确的目标让我们能理性地在「抽帧成本」「切断阈值」「人工投入」之间做权衡。
抽帧间隔是成本与覆盖率的直接权衡,而且无法两全。间隔大会漏掉一闪而过的违规画面,间隔小则三方机审的调用费用和处理延迟都上去(同时几千个房间在播)。我们的处理是「变化检测 + 分级」:画面变化大时加密、静态时降频;对高风险主播(历史有违规)加密抽帧,对可信主播降频。承认「一定会漏」,然后用分级把资源花在高风险的地方。
即时切断的阈值必须设得很高,因为误伤代价极大。切断意味着主播整场直播结束、当天收入没了、观众流失。所以只有「高危类型 + 高置信度」才切断,其余走「降权 + 人工复核」。宁可让中风险内容在小范围多存在几分钟,也不要误切一个正常主播。这个方向和点播审核相反(点播可以宁可拦下等人审),因为直播的误伤是不可逆的。
踩过的坑:切断推流后观众端显示「网络异常」,观众反复重进。切断的实现是调媒体服务器踢流,观众端拉不到流就走了「网络异常」的通用分支,观众以为是平台问题,反复退出重进,房间的连接数反而暴涨,而且客服收到一堆「你们直播打不开」的投诉。修法是切断时向观众端推送明确的状态与文案(「该直播因违规已结束」),并且阻止重进。教训是:任何「非正常结束」都必须给出明确的状态,让不出现「通用错误分支」兜底——通用分支会掩盖真实原因并引发错误的用户行为。
踩过的坑二:违规判定后礼物照常结算,主播拿到了违规获利。审核判定违规并切断了直播,但礼物结算是按场次异步跑的,没有和审核结论联动,钱正常打给主播了。后来复核确认违规,钱已经提现走了,追不回来。修法是判定违规立刻冻结本场结算,等复核结论出来再决定。教训是:内容处置和资金处置必须联动,任何一个环节漏了联动就会出现「违规获利」这类既是损失也是舆情风险的问题。
没做的部分:没做弹幕内容与画面的联合审核(有些违规是画面正常但弹幕引导的)。这需要把两路信号关联分析,复杂度较高,当时是各自独立审核。
下播到回放可看:从「房间状态置为已结束」到「回放可播放」的时间,一小时直播约 5 分钟。必须说明直播时长——这个耗时和时长基本成正比(要合并所有段并转码),说「5 分钟」不说时长没有意义。也要说明是否出多档(多档转码更慢)。
高危命中到推流切断:从「审核判定命中」到「媒体服务器确认踢流完成」的时间,10 秒内。要分段打点(判定、调用踢流接口、媒体服务器执行、观众端收到通知),因为面试官会问瓶颈在哪。另外要诚实说明「命中之前的时间」不在这个指标里——从违规行为发生到被抽帧捕获,取决于抽帧间隔,这段是真正的暴露时间。
暴露时间的完整口径:这是最该诚实的地方。真实的暴露时间 = 抽帧间隔(平均一半)+ 机审耗时 + 切断执行时间。只报「切断在 10 秒内」会给人「违规内容只暴露 10 秒」的错误印象。把完整口径说清楚,比报一个好看的分段数字可信。
「证据可回溯」怎么验证:随机抽已处置的案例,检查是否能调出命中帧、前后音视频片段、转写文本、命中规则与置信度、判定时刻。可以做成自动化校验:每条处置记录必须有完整的证据集,缺失就告警。
不要报「违规内容拦截率」或「审核准确率」。两个都需要「真实违规总量」作为分母,而那个数字不可知(没被发现的违规不在统计里)。可以报的绝对数:每日抽帧量、送审量、机审命中量、即时切断次数、转人工量、复核推翻量。其中「复核推翻量」最有价值——它直接反映误伤情况,而且报出来说明你在正视误判而不是只讲成绩。
没有匹配的内容,换个关键词试试。
项目拆解 · 直播互动与推拉流(后端)· 共 3 个模块 · 数字均为示例,需替换成自己项目的真实数据