怎么用 这是格式与深度的参考,不是让你照抄的模板。数字全部是示例,直接抄进简历一问就穿——必须换成你自己的真实数据,而且要能说清是怎么测出来的。下面每个模块都有「数字是怎么测的」和「面试追问」两段,写不出那两段的模块就不要往简历上写

面试题库(电商 / 短视频 / 社区 / 本地生活 / 旅游 / 企业服务 多业务场景)

项目背景设定 短视频平台的直播观众端,uni-app 打 App 与小程序。用户在直播间看直播、发弹幕、送礼物、上下滑切换直播间。信息流的视频播放性能在 短视频社区 App 那一页(点播场景),这一页讲的是直播(拉流场景,两者的难点完全不同);推拉流与弹幕礼物的服务端在 直播互动与推拉流,运营侧的中控台在 直播中控台
为什么选这三个模块 直播和点播在端上是两类问题:点播的核心是「预加载与缓存」(文件是固定的,可以提前下),直播的核心是「延迟与追帧」(流是实时的,缓冲多了延迟大、缓冲少了容易卡,这是一个必须权衡的矛盾)。三个模块分别是:起播速度与延迟的权衡(首帧要快、延迟要低、还不能卡,三个目标互相冲突)、高频弹幕与礼物动效(每秒几十条弹幕加连击礼物动效,低端机上是纯粹的渲染压力)、进退房与资源释放(用户上下滑快速切换直播间,播放器和长连接的资源如果不严格回收,滑几十个直播间就崩了)。

模块一:起播速度与延迟的权衡

  1. 起播速度与延迟的权衡(首帧优先的缓冲策略 + 追帧与倍速追赶 + 卡顿时自适应降档 + 断流重连与状态区分 + 起播失败的分层兜底)★★★
    简历这样写 直播拉流的起播速度与延迟控制(起播阶段小缓冲优先出首帧、稳态后动态调整缓冲水位 + 延迟超阈值时以倍速追帧而非丢帧跳变 + 连续卡顿触发码率自适应降档并在稳定后回升 + 断流与网络断开分别处理与退避重连 + 起播失败按原因分层兜底):直播端上的三个目标互相冲突——首帧要快(缓冲越小越快)、延迟要低(缓冲越小越低)、播放要不卡(缓冲越大越稳),早期使用固定缓冲策略导致弱网用户频繁卡顿而强网用户延迟偏大;因此改为分阶段策略:起播阶段用小缓冲优先出首帧,进入稳态后按实测网络抖动动态调整缓冲水位;当累计延迟超过阈值时用轻微倍速播放追帧而非直接丢帧跳变(后者会造成可感知的画面与声音突跳);连续卡顿时自适应降码率并在网络稳定后回升区分「主播断流」与「观众网络断开」两种情况并分别给出文案与重连策略;起播失败按原因分层兜底(切备用地址 / 降码率重试 / 提示主播未开播)。改造后首帧耗时与卡顿次数由固定策略改为按网络状况自适应,延迟追赶由丢帧跳变改为倍速平滑
    展开完整拆解
    为什么要这么设计

    先说清直播和点播在端上的根本差别,因为它决定了所有策略:点播的文件是固定的,可以提前下载、可以缓存、缓冲多大都不影响体验直播的流是实时产生的,缓冲了多少就等于延迟了多少。

    于是直播端上有三个目标,而它们互相冲突

    首帧要快——用户点进直播间希望立刻看到画面,而缓冲越小首帧越快。延迟要低——直播的互动性依赖低延迟(主播说「点赞过万我就唱」,弹幕延迟十秒就没意义了),缓冲越小延迟越低。播放要不卡——而这个恰恰要求缓冲越大越好,用缓冲吸收网络抖动。

    三个目标里前两个和第三个直接对立,没有一个固定的缓冲值能同时满足。第一版用了一个固定缓冲策略,结果弱网用户频繁卡顿(缓冲不够吸收抖动),而强网用户延迟偏大(缓冲白留了)——两头都不舒服。

    所以第一个设计是分阶段策略起播阶段用小缓冲,优先把首帧出来(用户对「点进去多久看到画面」最敏感);进入稳态后按实测的网络抖动动态调整缓冲水位——网络稳的用户缓冲小、延迟低;网络抖的用户缓冲大、不卡。把「一个固定值」换成「按实际情况调」。

    第二个设计是追帧。缓冲会累积——网络恢复后,之前积压的数据会让播放位置越来越落后于直播现场,延迟从两秒变成十几秒。必须追回来。

    追帧的方式很关键。第一版是直接丢帧跳到最新位置,快但画面和声音会有明显的突跳,用户的感受是「卡了一下」——而实际上是我们主动造成的。改成轻微倍速播放(比如 1.1 倍),在几秒内平滑地把延迟追回来,用户基本感觉不到只有延迟大到倍速追不回来时才丢帧。

    第三个是码率自适应:连续卡顿说明当前码率超出了用户的带宽,要降档;网络稳定一段时间后再回升。关键是回升要保守——刚稳定就升档,很可能立刻又卡,来回切换比一直低码率更难受。

    第四个容易被忽略但很影响体验:要区分「主播断流」和「观众网络断开」。这两种在技术上都表现为「拉不到数据」,但对用户来说完全不同:主播断流应该显示「主播网络不稳定,正在恢复」并保持在直播间等待;观众自己断网应该显示「你的网络已断开」并在恢复后重连。第一版统一显示「网络异常」,主播断流时一批用户以为自己网络有问题而退出了直播间。

    最后是起播失败的分层兜底:拉流地址不可用就切备用地址、码率太高起不来就降码率重试、主播确实没开播就明确提示而不是一直转圈

    整体链路
    先分清直播与点播的根本差别 ├─ 点播:文件固定,可预下载可缓存,缓冲多大都不影响体验 └─ 直播:流是实时产生的,缓冲了多少就等于延迟了多少 三个互相冲突的目标 ├─ 首帧要快 → 缓冲越小越快 ├─ 延迟要低 → 缓冲越小越低 │ 直播互动依赖低延迟 │ 主播说「点赞过万我就唱」,弹幕延迟十秒就没意义 └─ 播放不卡 → 缓冲越大越稳,用缓冲吸收网络抖动 → 前两个与第三个直接对立 → 没有一个固定缓冲值能同时满足 第一版固定缓冲策略的结果 ├─ 弱网用户频繁卡顿(缓冲不够吸收抖动) └─ 强网用户延迟偏大(缓冲白留了) → 两头都不舒服 一、分阶段缓冲策略 ├─ 起播阶段:小缓冲,优先出首帧 │ 用户对「点进去多久看到画面」最敏感 └─ 稳态阶段:按实测网络抖动动态调整水位 网络稳的用户:缓冲小、延迟低 网络抖的用户:缓冲大、不卡 → 把「一个固定值」换成「按实际情况调」 二、追帧(缓冲会累积,延迟会越来越大) │ ├─ 网络恢复后积压数据让播放位置落后于直播现场 │ 延迟从两秒变成十几秒,必须追回来 │ ├─ 第一版直接丢帧跳到最新位置 │ 快,但画面和声音有明显突跳 │ 用户感受是「卡了一下」 │ → 而实际上是我们主动造成的 │ ├─ 改为轻微倍速播放(如 1.1 倍) │ 几秒内平滑追回延迟,用户基本感觉不到 │ └─ 只有延迟大到倍速追不回来时才丢帧 三、码率自适应 ├─ 连续卡顿 → 说明码率超出带宽 → 降档 ├─ 网络稳定一段时间 → 回升 └─ 回升要保守 刚稳定就升档,很可能立刻又卡 来回切换比一直低码率更难受 四、区分断流原因(很影响体验,容易被忽略) │ ├─ 技术上都表现为「拉不到数据」,但对用户完全不同 │ ├─ 主播断流 → 「主播网络不稳定,正在恢复」 │ 保持在直播间等待 │ ├─ 观众断网 → 「你的网络已断开」 │ 网络恢复后重连 │ └─ 第一版统一显示「网络异常」 主播断流时一批用户以为自己网络有问题而退出了直播间 五、起播失败的分层兜底 ├─ 拉流地址不可用 → 切备用地址 ├─ 码率太高起不来 → 降码率重试 ├─ 主播确实没开播 → 明确提示,不要一直转圈 └─ 每一层失败都要有下一步,不要笼统的「加载失败」 上报(没有数据就调不了策略) ├─ 首帧耗时、卡顿次数与时长、延迟、码率切换次数 ├─ 按网络类型与机型分组看 └─ 这些数据是调缓冲策略的唯一依据
    分步拆解
    1. 先认清直播与点播的根本差别:直播缓冲了多少就等于延迟了多少。点播的优化经验(多预加载)在直播上是反的。
    2. 明确三个目标互相冲突:首帧快、延迟低、不卡顿。没有一个固定缓冲值能同时满足三者——这是所有策略的出发点。
    3. 起播阶段用小缓冲优先出首帧。用户对「点进去多久看到画面」最敏感,这一段体验决定他会不会留下
    4. 稳态后按实测网络抖动动态调整缓冲水位。把「一个固定值」换成「按实际情况调」,让强网用户享受低延迟、弱网用户不卡
    5. 做延迟监测与追帧。网络恢复后积压数据会让延迟持续变大,不追就会从两秒变成十几秒
    6. 追帧优先用轻微倍速播放,不要直接丢帧跳变。丢帧快但画面和声音有明显突跳,用户以为是卡了——而这是我们主动造成的
    7. 只有延迟大到倍速追不回来时才丢帧。倍速是首选、丢帧是兜底。
    8. 连续卡顿时降码率。卡顿说明当前码率超出了用户带宽,继续硬撑只会一直卡。
    9. 码率回升要保守。刚稳定就升档很可能立刻又卡,来回切换比一直低码率更难受——所以要求稳定持续一段时间才升。
    10. 区分「主播断流」和「观众网络断开」。技术上都是「拉不到数据」,但对用户完全不同
    11. 主播断流时保持在直播间等待并说明原因。第一版统一显示「网络异常」,一批用户以为自己网络有问题就退出了直播间
    12. 起播失败按原因分层兜底:切备用地址、降码率重试、明确提示未开播。不要一直转圈也不要笼统的「加载失败」。
    13. 上报首帧耗时、卡顿次数与时长、延迟、码率切换次数。没有这些数据压根调不了缓冲策略
    14. 上报数据要按网络类型与机型分组看。均值会把弱网用户的问题平掉。
    15. 各端播放器能力差异要抽象。小程序与 App 的播放器组件能力不同(能不能设倍速、能不能拿到缓冲水位),策略要能在能力受限的端上降级
    关键决策与取舍

    追帧用倍速而不用丢帧,这是这个模块最值得讲的取舍。丢帧的实现最简单(直接 seek 到最新位置),而且追赶速度最快。但它的代价是用户可感知的突跳——画面跳一下、声音断一下,而用户会把这个归因为「卡了」,也就是说我们为了解决延迟主动制造了一次卡顿的观感。倍速追帧慢一些(要几秒),但整个过程用户基本感觉不到取舍依据是:用户感知不到的慢,优于用户感知到的快。这条判断在很多平滑处理上都成立(地图点位插值也是同一个思路:宁可显示推算位置,也不要让点位跳变)。但要保留丢帧作为兜底——延迟大到几十秒时倍速追不回来,此时突跳一次是必要的。

    码率回升比降档保守,这是不对称的。降档要果断(连续卡顿就降,因为继续硬撑只会一直卡);回升要保守(要求网络稳定持续一段时间)。原因是「频繁切换」本身就是一种糟糕的体验——每次切换都可能伴随短暂的画面变化,来回切换比稳定在低码率更让人难受。这个不对称的依据是「切换本身有成本」,所以要减少切换次数而不只是追求「每一刻都用最高可用码率」。

    区分断流原因,这不是技术优化而是归因的准确性。技术上两种情况都是「拉不到数据」,要区分需要额外的信号(服务端下发主播状态、或者根据自身网络探测判断)。做这个区分的价值是把「用户误判」消掉:主播断流时说「你的网络异常」,用户会去检查自己的网络甚至退出直播间;而如果告诉他「主播网络不稳定,正在恢复」,他会等代价是要多拿一个信号并处理两种状态的文案与行为,收益是留住了这批本来会流失的观众。

    踩过的坑:固定缓冲策略导致两头不满意。弱网用户频繁卡顿(缓冲不够吸收抖动),强网用户延迟偏大(缓冲白留了,互动体验受损)。修法是分阶段加动态水位教训是:当多个目标互相冲突时,用一个固定参数一定会同时得罪两端——正确的方向是让参数随实际情况变化,而这要求先有上报数据能知道「实际情况」是什么

    踩过的坑二:丢帧追赶被用户当成卡顿投诉。我们为了压延迟做了丢帧追赶,结果卡顿类的反馈反而变多了——用户看到画面和声音突跳,理解成「卡了」。我们做的优化被算成了 bug。修法是改用倍速追帧教训是:优化的效果要按「用户的感知」来评估而不是按「指标的数值」——延迟指标确实降了,但用户的体验评分变差了,这种情况下指标是在骗自己

    踩过的坑三:主播断流时显示「网络异常」,观众误以为是自己的问题而退出。主播那边网络抖动断流几十秒,我们的观众端统一显示「网络异常,请检查网络」,一批观众去重启路由器、切换网络,最后干脆退出了直播间;等主播恢复时人已经走了一批。修法是区分断流原因并给出对应文案教训是:错误提示的准确性直接影响用户的行为选择——一个归因错误的提示会引导用户做无用甚至有害的动作。

    踩过的坑四:码率回升太激进,形成来回切换。网络刚稳定就升档,升上去立刻又卡、又降下来,几分钟内切换十几次,用户的反馈是「画质一直在变,很难受」。修法是要求稳定持续一段时间才升,并且限制单位时间内的切换次数教训是:自适应策略必须考虑「切换本身的成本」,只优化「每一刻的最优」会导致震荡——这和自动降级要有恢复条件、但恢复条件要比降级条件严格,是同一个道理。

    没做的部分:没做低延迟直播协议(把延迟压到一秒内)。它需要服务端与端上都换协议栈,而且在 uni-app 的小程序端支持有限,当时判断投入产出不合适。也没做多主播连麦的多路播放,属于产品没做的功能。

    数字是怎么测的

    首帧耗时:从点击进入到首帧渲染的耗时按网络类型与机型分组报 P50 与 P90。必须分组——整体均值会把弱网用户的问题平掉,而弱网用户恰恰是要优化的对象。

    卡顿:单位时长内的卡顿次数与累计卡顿时长,同样分网络类型看。两个指标都要报:次数多但每次很短,和次数少但每次很长,体验完全不同。

    延迟:播放位置与直播现场的时间差分布并且要报追帧的效果——延迟超阈值后多久追回到正常区间。

    追帧方式的体验对比:这一项要报「卡顿类反馈的数量」而不是延迟数值——因为丢帧追赶时延迟指标是好的,但卡顿反馈反而变多了这个对比本身就说明「指标好不等于体验好」,是这一页最值得讲的数据故事。

    码率切换次数:单次观看内的码率切换次数分布这个数字要控制住——切换太频繁本身就是糟糕体验,回升保守之后它应该明显下降。

    断流归因的效果:主播断流期间观众的退出率,改造前后对比。这是「区分断流原因」这个改动的直接业务收益,而且能明确归因。

    起播失败率与失败原因分布:按原因分开报(地址不可用、码率过高、主播未开播),以及各层兜底的成功率

    不要报「首帧 200ms 以内」这类单一数字。首帧高度依赖网络与机型,脱离条件的单一数值没有意义也不要报「零卡顿」——弱网下卡顿是物理存在的。正确表述是「按网络与机型分组的首帧与卡顿分布、延迟追赶从丢帧改为倍速后卡顿类反馈的变化、码率切换次数的下降、主播断流期间退出率的变化」。

    面试追问
    Q:直播的播放优化和点播有什么不一样? A:根本差别是「点播的文件是固定的,可以提前下载、缓冲多大都不影响体验;直播的流是实时产生的,缓冲了多少就等于延迟了多少」。所以点播的优化经验(多预加载、大缓冲)在直播上是反的。直播端上有三个互相冲突的目标首帧要快(缓冲越小越快)、延迟要低(缓冲越小越低,而直播的互动性依赖低延迟——主播说「点赞过万我就唱」,弹幕延迟十秒就没意义了)、播放不卡(这个恰恰要求缓冲越大越好,用缓冲吸收网络抖动)。前两个和第三个直接对立,没有一个固定缓冲值能同时满足。我们第一版用固定缓冲策略,结果弱网用户频繁卡顿、强网用户延迟偏大,两头都不舒服。改成分阶段策略:起播阶段小缓冲优先出首帧,稳态后按实测网络抖动动态调整水位教训是:当多个目标互相冲突时,用一个固定参数一定会同时得罪两端——正确方向是让参数随实际情况变化,而这要求先有上报数据能知道「实际情况」是什么
    Q:延迟越来越大怎么追回来? A:优先用轻微倍速播放,不要直接丢帧跳变——这是我踩坑之后最想讲的一个取舍。延迟会累积:网络恢复后积压的数据让播放位置越来越落后于直播现场,延迟从两秒变成十几秒。第一版的做法是直接丢帧 seek 到最新位置,实现最简单、追赶最快;但结果是卡顿类的反馈反而变多了——用户看到画面和声音突跳,理解成「卡了」,我们做的优化被算成了 bug。改成轻微倍速播放(如 1.1 倍),几秒内平滑追回,用户基本感觉不到取舍依据是:用户感知不到的慢,优于用户感知到的快。这条判断在很多平滑处理上都成立(我在地图海量点位上也是同一个思路:宁可显示插值推算的位置,也不要让点位跳变)。但要保留丢帧作为兜底——延迟大到几十秒时倍速追不回来,此时突跳一次是必要的。更大的教训是:优化效果要按「用户的感知」评估而不是按「指标的数值」——延迟指标确实降了但体验变差了,这种情况下指标是在骗自己
    Q:网络变差了怎么处理?码率要不要自动切? A:要切,但降档和回升的策略必须不对称——降档果断、回升保守。降档要果断:连续卡顿说明当前码率超出了用户带宽,继续硬撑只会一直卡回升要保守,这是我们踩坑改的:最初网络刚稳定就升档,升上去立刻又卡、又降下来,几分钟内切换十几次,用户反馈「画质一直在变,很难受」。修法是要求稳定持续一段时间才升,并且限制单位时间内的切换次数不对称的依据是「切换本身有成本」——每次切换都可能伴随短暂的画面变化,频繁切换比稳定在低码率更让人难受;所以目标应该是减少切换次数,而不是追求「每一刻都用最高可用码率」。教训是:自适应策略必须考虑切换本身的成本,只优化「每一刻的最优」会导致震荡——这和「自动降级必须配自动恢复,但恢复条件要比降级条件严格」是完全同一个道理。度量上我会专门报「单次观看内的码率切换次数分布」,因为这个数字本身就是体验指标,不只是过程指标。
    Q:画面卡住了,怎么告诉用户发生了什么? A:必须区分「主播断流」和「观众网络断开」,这两种技术上都是「拉不到数据」但对用户完全不同。我们踩过很实在的坑:主播那边网络抖动断流几十秒,我们的观众端统一显示「网络异常,请检查网络」,一批观众去重启路由器、切换网络,最后干脆退出了直播间;等主播恢复时人已经走了一批。正确做法主播断流 → 「主播网络不稳定,正在恢复」并保持在直播间等待观众自己断网 → 「你的网络已断开」并在恢复后重连教训是:错误提示的准确性直接影响用户的行为选择——一个归因错误的提示会引导用户做无用甚至有害的动作代价是要多拿一个信号(服务端下发主播状态,或根据自身网络探测判断)并处理两种状态的文案与行为,收益是留住了这批本来会流失的观众——度量上我会报「主播断流期间观众的退出率」改造前后对比,这是能明确归因的业务收益。起播失败也要同样分层:地址不可用切备用、码率过高降档重试、主播确实没开播就明确提示,不要一直转圈也不要笼统的「加载失败」

模块二:高频弹幕与礼物动效

  1. 高频弹幕与礼物动效(弹幕合并与丢弃策略 + 定长池复用不新建节点 + 动效串行队列与连击合并 + 按机型分档降级 + 动效不阻塞互动)★★★
    简历这样写 直播间高频弹幕与礼物动效的渲染治理(弹幕按帧合并上屏与超限丢弃策略 + 定长节点池复用避免频繁创建销毁 + 礼物动效串行队列与同人连击合并 + 按机型能力分档降级动效 + 动效层与互动层分离且不拦截点击 + 页面不可见时暂停动画):热门直播间弹幕可达每秒数十条并伴随连击礼物动效,早期逐条创建弹幕节点并对每份礼物独立播放动效,低端机上出现明显掉帧、内存增长与点击无响应;因此把弹幕改为按帧合并上屏(同一帧内的多条一次性处理)并设上屏条数上限与超限丢弃策略(弹幕是可丢的,保证流畅优先于保证全展示),节点使用定长池复用;礼物动效改为串行队列并对同一用户的连击合并为一次带计数的动效,按机型能力分档决定动效复杂度(低端机仅显示简化提示);动效层与互动层分离,保证动效播放期间点赞、送礼、评论输入不被遮挡也不被拦截;页面不可见时暂停全部动画。改造后低端机在高弹幕量下的帧率由明显掉帧改为可接受区间,动效期间的点击无响应由层级分离消除
    展开完整拆解
    为什么要这么设计

    热门直播间的渲染压力和普通页面完全不是一个量级:弹幕每秒几十条持续不断礼物动效可能连击几十次而这些是叠加在一个正在播放视频的页面上的。视频解码本身就占着资源,动画只能用剩下的。

    第一版的实现很直接:每条弹幕创建一个节点、每份礼物播放一次动效。在测试的中高端机上没问题,上线后低端机的反馈很集中:卡、内存涨、点不动

    第一个问题是弹幕逐条处理。每秒几十条,就是每秒几十次节点创建、样式计算、动画启动。而且这些操作分散在一秒内的各个时刻,每一次都可能打断当前帧

    解法有三层。按帧合并:把同一帧内到达的多条弹幕攒起来一次性处理,让处理次数与帧率同阶而不是与消息条数同阶(这和地图点位的帧内合并重绘是同一个手段)。节点池复用:维护一个定长的节点池,弹幕飘出屏幕后节点归还池中重用,而不是销毁再新建上屏上限与丢弃:屏幕上同时显示的弹幕有数量上限,超出的直接丢弃

    「丢弃」这个决定很重要,它基于一个业务判断:弹幕是可丢的。用户看直播时不会逐条读弹幕,他感受的是「氛围热闹」;丢掉一部分不影响这个感受,而卡顿会直接毁掉它所以保证流畅优先于保证全展示。(对比:私信消息不能丢,因为用户会逐条读。同样是消息,可丢性完全不同,处理策略也就不同。

    第二个问题是礼物动效。第一版每份礼物独立播放,用户连击三十次就有三十个动效同时在播,画面糊成一片而且帧率崩掉。

    解法是串行队列加连击合并:动效进队列依次播放而不是同时播同一用户的连续送礼合并成一次带计数的动效(显示「x30」而不是播三十遍)。合并不只是性能优化,它的展示效果反而更好——一个「x30」比三十个重叠的动效更能表达「这人送了很多」。

    第三个问题是低端机压根撑不住任何复杂动效。所以要按机型能力分档:高端机播完整动效、中端机播简化版、低端机只显示一条文字提示分档的依据要用实测的设备能力而不是价格或年份

    第四个问题最容易被忽略但用户最恼火:动效播放期间点不动东西。全屏礼物动效盖住了整个屏幕,用户想点评论、想送礼,全被动效层拦住了——而这正是他最想互动的时刻。解法是动效层与互动层严格分离,动效层不拦截点击事件。

    最后是页面不可见时暂停全部动画。用户切到后台或者最小化,动画还在跑就是纯耗电

    整体链路
    压力来源(和普通页面不是一个量级) ├─ 弹幕每秒几十条持续不断 ├─ 礼物动效可能连击几十次 └─ 而这些叠加在一个正在播放视频的页面上 视频解码本身就占着资源,动画只能用剩下的 第一版:逐条创建弹幕节点、每份礼物播一次动效 ├─ 测试的中高端机没问题 └─ 上线后低端机反馈很集中:卡、内存涨、点不动 一、弹幕的三层处理 │ ├─ 按帧合并上屏 │ 同一帧内到达的多条攒起来一次性处理 │ 处理次数与帧率同阶,不与消息条数同阶 │ → 和地图点位的帧内合并重绘是同一个手段 │ ├─ 定长节点池复用 │ 弹幕飘出屏幕后节点归还池中重用 │ 不销毁再新建 │ └─ 上屏条数上限 + 超限丢弃 │ └─ 「丢弃」基于一个业务判断:弹幕是可丢的 用户不会逐条读弹幕,他感受的是「氛围热闹」 丢一部分不影响这个感受,卡顿会直接毁掉它 → 保证流畅优先于保证全展示 → 对比:私信消息不能丢,因为用户会逐条读 → 同样是消息,可丢性不同,策略就不同 二、礼物动效 │ ├─ 第一版每份礼物独立播放 │ 用户连击三十次就有三十个动效同时在播 │ 画面糊成一片而且帧率崩掉 │ ├─ 改为串行队列:依次播放,不同时播 │ └─ 同一用户的连续送礼合并成一次带计数的动效 显示「x30」而不是播三十遍 → 合并不只是性能优化,展示效果反而更好 → 一个「x30」比三十个重叠动效更能表达「送了很多」 三、按机型能力分档降级 ├─ 高端机:完整动效 ├─ 中端机:简化版 ├─ 低端机:只显示一条文字提示 └─ 分档依据要用实测的设备能力,不用价格或年份 四、动效层与互动层分离(最容易忽略,用户最恼火) │ ├─ 全屏礼物动效盖住整个屏幕 │ 用户想点评论、想送礼,全被动效层拦住 │ → 而这正是他最想互动的时刻 │ └─ 动效层不拦截点击事件 互动控件始终可点、不被遮挡 五、页面不可见时暂停全部动画 └─ 切后台或最小化后动画还在跑就是纯耗电 上报(要能定位到机型) ├─ 帧率、内存、弹幕丢弃率、动效降级档位分布 └─ 按机型分组看,均值会掩盖低端机的问题
    分步拆解
    1. 先认清压力来源:弹幕与动效是叠加在一个正在解码视频的页面上的。视频本身占着资源,动画只能用剩下的——这一条决定了必须比普通页面更激进地控制开销。
    2. 弹幕按帧合并上屏,同一帧内的多条一次性处理。让处理次数与帧率同阶而不是与消息条数同阶——和地图点位的帧内合并是同一个手段。
    3. 用定长节点池复用弹幕节点。飘出屏幕后归还池中重用,不要销毁再新建
    4. 设置上屏条数上限,超出的直接丢弃。
    5. 丢弃的依据是一个业务判断:弹幕是可丢的。用户不逐条读弹幕,他感受的是「氛围热闹」,丢一部分不影响这个感受,而卡顿会直接毁掉它
    6. 注意对比:私信消息不能丢,因为用户会逐条读。同样是消息,可丢性完全不同,处理策略也就不同——这个区分要能说清。
    7. 礼物动效走串行队列,依次播放而不是同时播。第一版连击三十次就有三十个动效同时在播,画面糊成一片
    8. 同一用户的连续送礼合并成一次带计数的动效。显示「x30」而不是播三十遍。
    9. 合并的收益是双重的:性能更好,展示效果也更好。一个「x30」比三十个重叠动效更能表达「这人送了很多」——这是少见的「优化同时改善体验」的情况。
    10. 按机型能力分档决定动效复杂度。高端完整、中端简化、低端只显示文字提示
    11. 分档依据要用实测的设备能力,不要用价格或年份。同价位不同芯片的差异很大。
    12. 动效层与互动层严格分离,动效层不拦截点击。第一版全屏动效盖住屏幕,用户想点评论想送礼全被拦住——而这正是他最想互动的时刻
    13. 互动控件在动效期间要始终可见可点。不只是「能点」,还要「看得见」。
    14. 页面不可见时暂停全部动画。切后台后动画还在跑就是纯耗电
    15. 上报帧率、内存、弹幕丢弃率、动效降级档位分布,并按机型分组。均值会掩盖低端机的问题,而低端机才是要解决的对象。
    关键决策与取舍

    弹幕可以丢,这是这个模块最重要的业务判断。技术上可以做到「一条不丢」(排队慢慢上屏),但那样弹幕会严重滞后于直播现场,用户看到的是十几秒前的评论,互动感就没了。丢弃则是牺牲完整性换取实时性与流畅度依据是「用户如何消费这些信息」:弹幕是氛围性的、被扫视的、不需要完整;而私信是逐条阅读的、必须完整同样是「高频消息 + 长列表」,可丢性不同,策略就完全不同——我在私信那边做的是「seq 空洞检测与补齐」,这里做的是「超限丢弃」,方向完全相反而依据是同一条:看用户怎么消费它。

    连击合并是少见的「性能与体验同向」的优化。大多数性能优化都要牺牲一点体验(降码率画质变差、降级动效变简单)。而连击合并反而让表达更清楚——三十个重叠动效是视觉噪音,一个「x30」是清晰的信息。这提示我一个判断方法:遇到性能问题时先问「现在的展示方式本身是不是有问题」,有时性能问题的根源是展示设计不合理,改对了展示,性能问题自然消失,而不需要做取舍。

    按机型分档而不是统一降级或统一不降。统一用低配动效对高端机用户是浪费(他们的设备能承受更好的效果);统一用高配则低端机崩。分档的代价是要维护多套动效资源与一套设备能力判定,而判定本身不可能完全准确。缓解手段是运行时监控实际帧率,如果分档判错了(判成高端但实际卡),运行时自动往下降——静态分档加运行时动态调整,比只靠其中一种可靠

    踩过的坑:动效层拦截点击,用户在最想互动的时刻点不动东西。全屏礼物动效播放期间盖住了整个屏幕,用户想点评论、想跟着送礼,全被动效层拦住了这个坑的代价被严重低估——礼物动效播放的时刻恰恰是直播间气氛最热、用户互动意愿最强的时刻,而我们在那一刻把交互堵死了,直接损失的是送礼收入。修法是动效层与互动层严格分离,动效层不拦截点击教训是:全屏视觉元素必须明确它对交互的影响,「好看」不能以「不能用」为代价——尤其当它出现的时机正好是用户最想操作的时候。

    踩过的坑二:逐条创建弹幕节点,低端机上内存持续增长。每秒几十条弹幕各创建一个节点,飘出屏幕后销毁,看起来该释放了;但频繁的创建销毁导致内存抖动严重,而且部分节点因为动画未结束就被移除而残留引用,观看十几分钟后明显卡顿。修法是定长节点池复用教训是:高频创建销毁的对象要用池化,而不是指望垃圾回收跟上——这和播放器实例池化、和「多媒体资源必须显式回收」是同一类判断。

    踩过的坑三:连击礼物同时播放,画面糊成一片而且帧率崩掉。用户连击三十次,三十个动效同时在播,什么都看不清,低端机直接掉到个位数帧率。修法是串行队列加连击合并教训是:同类事件的高频发生要先考虑「合并」而不是「并发处理」——合并往往同时解决性能和表达两个问题。

    踩过的坑四:按机型价格分档,判断结果和实际能力不符。我们最初用机型价格区间粗略分档,结果一批低价但芯片不错的机型被判成低端、只能看文字提示,用户抱怨「为什么我看不到动效」;反过来也有高价老机型被判成高端然后卡。修法是用实测的设备能力分档,并且加运行时帧率监控做动态调整教训是:设备分档不能用价格或年份这类间接指标,要用能力实测;而且静态分档一定会有判错的,必须配运行时的动态修正。

    没做的部分:没做弹幕的智能优先级(把关注的人、大额送礼的弹幕优先保留)。丢弃策略目前是简单的超限丢新,按优先级丢会更好但需要弹幕携带更多信息。也没做动效资源的动态下发(新礼物动效不用发版),需要资源包管理与安全校验,工作量不小。

    数字是怎么测的

    帧率:必须按机型分组报,并且要报低端机的数据。报在指定弹幕量与动效强度下的帧率,改造前后对比。整体均值毫无意义——问题只出在低端机上,均值会把它平掉。

    内存:连续观看一段时间后的内存曲线,改造前是持续增长(逐条创建销毁导致抖动与残留),改造后平稳。用曲线形态而不是峰值数字。

    弹幕丢弃率:要主动报,而且要说明「弹幕可丢」这个判断的依据。报在不同弹幕量下的丢弃比例——它是一个被有意设计的行为,不是缺陷,主动交代比等面试官问出来好

    动效降级分布:各档位的设备占比,以及运行时动态下降的触发次数。后者说明静态分档判错的比例,诚实报出来比声称分档很准可信

    动效期间的点击响应:确定性验证——动效播放期间点击评论、送礼、点赞,检查全部正常响应且控件可见这是踩坑后必须固化的用例,因为它在功能测试里很容易被忽略(没人会在动效播放时去点)。

    互动收入的影响:如果能拿到数据,报动效期间的送礼行为变化这是「动效不拦截交互」这个改动最直接的业务收益,而且能明确归因。

    不要报「支持无限弹幕」或「零掉帧」。低端机的渲染能力是硬约束,而且我们本来就设计了丢弃正确表述是「按机型分组的帧率变化、内存曲线由增长变平稳、弹幕丢弃率与其业务依据、动效降级的档位分布与运行时修正次数、动效期间交互可用由用例保证」。

    面试追问
    Q:弹幕每秒几十条,怎么保证不卡? A:三层处理,而最关键的一层是「接受丢弃」。一、按帧合并上屏:同一帧内到达的多条攒起来一次性处理,让处理次数与帧率同阶而不是与消息条数同阶(这和我在地图海量点位上的帧内合并重绘是同一个手段)。二、定长节点池复用:弹幕飘出屏幕后节点归还池中重用,不销毁再新建——我们踩过这个坑,逐条创建销毁导致内存抖动严重,而且部分节点因动画未结束就被移除而残留引用,观看十几分钟后明显卡顿。三、上屏条数上限加超限丢弃。丢弃这个决定基于一个业务判断:弹幕是可丢的——用户不会逐条读弹幕,他感受的是「氛围热闹」,丢一部分不影响这个感受,而卡顿会直接毁掉它我想强调一个对比:私信消息我做的是「seq 空洞检测与主动补齐」,这里做的是「超限丢弃」,方向完全相反,而依据是同一条:看用户怎么消费它——弹幕是被扫视的、氛围性的;私信是逐条阅读的、必须完整。同样是「高频消息加长列表」,可丢性不同,策略就完全不同。
    Q:用户连击送了三十个礼物,动效怎么处理? A:串行队列加连击合并——而这是一个少见的「性能与体验同向」的优化。第一版每份礼物独立播放,连击三十次就有三十个动效同时在播,什么都看不清,低端机直接掉到个位数帧率。改成动效进串行队列依次播放,并且同一用户的连续送礼合并成一次带计数的动效——显示「x30」而不是播三十遍。关键是合并的收益是双重的:性能更好,而且展示效果反而更好——一个「x30」比三十个重叠动效更能表达「这人送了很多」大多数性能优化都要牺牲一点体验(降码率画质变差、降级动效变简单),而这个不用这提示我一个判断方法:遇到性能问题时先问「现在的展示方式本身是不是有问题」——有时性能问题的根源是展示设计不合理,改对了展示,性能问题自然消失,压根不需要做取舍教训还有一条:同类事件的高频发生要先考虑「合并」而不是「并发处理」,合并往往同时解决性能和表达两个问题。
    Q:低端机压根跑不动这些动效怎么办? A:按机型能力分档,但静态分档一定会判错,所以必须配运行时动态修正。分档是:高端机完整动效、中端机简化版、低端机只显示一条文字提示为什么不统一处理:统一用低配对高端机用户是浪费(他们的设备能承受更好效果),统一用高配则低端机崩。我们踩过分档依据的坑:最初用机型价格区间粗略分档,结果一批低价但芯片不错的机型被判成低端、只能看文字提示,用户抱怨「为什么我看不到动效」;反过来也有高价老机型被判成高端然后卡。修法是用实测的设备能力分档,并且加运行时帧率监控做动态调整——如果分档判成高端但实际卡,运行时自动往下降教训两条设备分档不能用价格或年份这类间接指标,要用能力实测静态分档一定会有判错的,必须配运行时的动态修正——静态加动态比只靠其中一种可靠度量上我会报「各档位的设备占比」和「运行时动态下降的触发次数」,后者说明静态分档判错的比例,诚实报出来比声称分档很准可信
    Q:全屏礼物动效播放时,用户还能操作吗? A:必须能,而且这是我们代价最大也最容易被低估的一个坑。第一版全屏礼物动效盖住了整个屏幕,用户想点评论、想跟着送礼,全被动效层拦住了这个坑的代价被严重低估——礼物动效播放的时刻恰恰是直播间气氛最热、用户互动意愿最强的时刻,而我们在那一刻把交互堵死了,直接损失的是送礼收入。修法是动效层与互动层严格分离,动效层不拦截点击事件,而且互动控件在动效期间要始终可见可点——不只是「能点」,还要「看得见」。教训是:全屏视觉元素必须明确它对交互的影响,「好看」不能以「不能用」为代价,尤其当它出现的时机正好是用户最想操作的时候。验证上这一条必须做成常驻用例——动效播放期间点击评论、送礼、点赞,检查全部正常响应且控件可见,因为它在功能测试里很容易被忽略(没人会专门在动效播放时去点)顺带一个基本项:页面不可见时要暂停全部动画,切后台后动画还在跑就是纯耗电。

模块三:进退房与资源释放

  1. 进退房与资源释放(播放器单例复用 + 退房清单化释放 + 滑动防抖与预建连 + 长连接按房间切换而非重建 + 泄漏自检)★★★
    简历这样写 直播间快速切换下的资源生命周期治理(播放器单例复用只换流地址 + 退房释放走清单化流程并覆盖播放器/长连接订阅/动效队列/定时器 + 上下滑切换防抖与提前建连 + 长连接按房间切换订阅而非断开重建 + 连续切换后的内存自检与告警):用户上下滑快速切换直播间时每个房间都会创建播放器、建立订阅、启动动效与定时器,早期退房释放不完整,连续切换数十个直播间后出现内存持续增长、旧房间弹幕串入新房间、以及标签页或应用被系统回收;因此把播放器改为单例复用只换流地址,把退房释放做成清单化流程(播放器解绑与停止、长连接订阅退订、动效队列清空、定时器清理、节点池归还)并在每次退房时逐项执行;上下滑切换加防抖避免快速滑动时对每个中间房间都发起建连,同时对即将进入的房间提前建连以缩短起播;长连接按房间切换订阅而不是断开重建;上线后加连续切换的内存自检,超阈值上报。改造后连续切换数十个直播间的内存由持续增长变为平稳,旧房间弹幕串入由订阅按房间切换与队列清空消除
    展开完整拆解
    为什么要这么设计

    直播间的交互形态是上下滑切换——和刷短视频一样。这就意味着用户可能在一分钟内滑过十几个直播间,而每个直播间都不是一个轻量页面:它有一个播放器在拉流、一个长连接订阅在收弹幕、一个动效队列在跑、还有若干定时器(倒计时、心跳、数据刷新)。

    第一版的问题是进房时创建得很完整,退房时释放得不完整。表现有三类。

    第一是内存持续增长。连续滑二三十个直播间后内存明显上涨,最后应用被系统回收或者标签页崩溃。查下来是好几处没释放:播放器实例、长连接的订阅回调、动效队列里未播完的任务、以及定时器。单独看每一处泄漏都不大,累积几十个房间就致命了。

    第二是旧房间的弹幕串进了新房间。用户滑到新直播间,看到的弹幕里混着上一个直播间的内容。原因是退房时没有退订旧房间的消息订阅,而新房间又订阅了自己的,于是两个房间的消息都进来了。这个 bug 用户感知非常明显,会被理解成「串台」这种严重问题。

    第三是快速滑动时对每个中间房间都发起了建连。用户快速连滑五个房间,我们对五个房间都创建了播放器、都建了连接——而他只在最后一个停留。中间四个的建连是纯浪费,而且它们的创建与销毁本身就在制造卡顿,让滑动更不流畅。

    所以四个设计。

    第一是播放器单例复用:全局只有一个播放器实例,切房间时只换流地址而不销毁重建这和直播中控台的播放器实例池化是同一个思路,只是这里更简单(同时只需要一个)。

    第二也是最重要的:把退房释放做成清单化流程。不要靠「在组件卸载钩子里写几行清理」——那种写法一定会漏,因为新增一个资源时没人记得回去补清单化的意思是:有一个明确的资源清单(播放器、订阅、动效队列、定时器、节点池),退房时逐项执行,新增资源必须加进清单。

    第三是滑动防抖加提前建连。快速滑动时只对「停下来的那个房间」建连;同时对即将进入的下一个房间提前建连(用户滑动的方向是可预测的),这样停下来时首帧更快。防抖和预建连看起来矛盾,实际是配合的:防抖避免对中间房间浪费,预建连只针对最可能进入的那一个。

    第四是长连接按房间切换订阅而不是断开重建。长连接本身是应用级的(和 IM 那边一样),切房间只需要退订旧房间、订阅新房间,不需要断开连接再重连——重连的代价远大于换订阅

    最后加了一道自检连续切换若干个房间后检查内存,超阈值就上报。因为泄漏这类问题在开发时切三五个房间测不出来,只在真实使用的连续切换下才暴露,必须有线上数据兜住。

    整体链路
    交互形态决定了问题(上下滑切换直播间) ├─ 用户可能一分钟内滑过十几个直播间 └─ 而每个直播间不是轻量页面 一个播放器在拉流 一个长连接订阅在收弹幕 一个动效队列在跑 若干定时器(倒计时、心跳、数据刷新) 第一版:进房创建得很完整,退房释放得不完整 │ ├─ 内存持续增长 │ 连续滑二三十个房间后内存明显上涨 │ 最后应用被系统回收或标签页崩溃 │ 漏释放的有:播放器实例、订阅回调 │ 动效队列未播完的任务、定时器 │ → 单独看每一处都不大,累积几十个房间就致命 │ ├─ 旧房间弹幕串进新房间 │ 退房时没退订旧房间的消息订阅 │ 新房间又订阅了自己的 → 两个房间消息都进来 │ → 用户感知非常明显,会被理解成「串台」 │ └─ 快速滑动时对每个中间房间都建连 连滑五个房间就对五个都创建播放器、都建连 而他只在最后一个停留 → 中间四个是纯浪费 → 而且创建销毁本身在制造卡顿,让滑动更不流畅 一、播放器单例复用 ├─ 全局只有一个播放器实例 ├─ 切房间只换流地址,不销毁重建 └─ 和直播中控台的实例池化同思路,这里更简单(只需一个) 二、退房释放做成清单化流程(最重要) │ ├─ 不要靠「在组件卸载钩子里写几行清理」 │ 那种写法一定会漏 │ 因为新增一个资源时没人记得回去补 │ ├─ 明确的资源清单,退房时逐项执行 │ 播放器:停止 + 解绑媒体源 │ 长连接:退订该房间 │ 动效队列:清空未播完的任务 │ 定时器:全部清理 │ 节点池:归还并重置 │ └─ 新增资源必须加进清单,这是流程约定 三、滑动防抖 + 提前建连(看似矛盾实为配合) ├─ 防抖:只对「停下来的那个房间」建连 ├─ 预建连:对即将进入的下一个房间提前建连 │ 用户滑动的方向是可预测的 ├─ 防抖避免对中间房间浪费 └─ 预建连只针对最可能进入的那一个 四、长连接按房间切换订阅,不断开重建 ├─ 长连接本身是应用级的(和 IM 一样) ├─ 切房间只需退订旧房间、订阅新房间 └─ 重连的代价远大于换订阅 五、连续切换的内存自检 ├─ 切换若干个房间后检查内存,超阈值上报 └─ 因为泄漏在开发时切三五个房间测不出来 只在真实使用的连续切换下才暴露 → 必须有线上数据兜住 退房还要处理的 ├─ 未发送完的弹幕、未完成的送礼请求要有明确归属 │ 退房不等于取消,已发出的请求要正常完成 ├─ 退房时的埋点(观看时长)要在释放前上报 └─ 从后台返回时要判断当前房间是否还有效 主播可能已经下播
    分步拆解
    1. 先认清交互形态:用户可能一分钟内滑过十几个直播间,而每个都不是轻量页面。播放器、长连接订阅、动效队列、定时器全都要创建和释放
    2. 播放器改成单例复用,切房间只换流地址不销毁重建。和直播中控台的实例池化同思路。
    3. 把退房释放做成清单化流程,这是最重要的一条。不要靠「在卸载钩子里写几行清理」——那种写法一定会漏,因为新增资源时没人记得回去补
    4. 清单要覆盖:播放器停止与解绑、长连接退订、动效队列清空、定时器清理、节点池归还。逐项执行。
    5. 播放器要「停止加解绑媒体源」,不能只停止。暂停的播放器仍可能持有解码器与缓冲——这和中控台踩过的坑是同一个。
    6. 长连接必须退订旧房间。不退订会导致旧房间的弹幕串进新房间,用户会理解成「串台」这种严重问题
    7. 动效队列要清空未播完的任务。否则新房间会突然播出上个房间的礼物动效
    8. 把「新增资源必须加进清单」定成流程约定。这是唯一能防止未来再漏的方式。
    9. 上下滑切换加防抖,只对停下来的房间建连。快速连滑五个房间时,中间四个的建连是纯浪费,而且创建销毁本身在制造卡顿
    10. 对即将进入的下一个房间提前建连。用户滑动方向可预测,预建连能明显缩短停下来时的起播时间
    11. 理解防抖和预建连不矛盾:防抖避免对中间房间浪费,预建连只针对最可能进入的那一个。
    12. 长连接按房间切换订阅而不是断开重建。长连接是应用级资源,重连的代价远大于换订阅
    13. 加连续切换的内存自检,超阈值上报。因为泄漏在开发时切三五个房间压根测不出来,只在真实连续切换下才暴露
    14. 退房不等于取消已发出的请求。未完成的送礼请求要正常完成并有明确的结果归属,不能因为退房就丢掉——那可能是一笔已扣款的交易。
    15. 退房埋点(观看时长)要在释放资源之前上报。先释放再上报会拿不到数据。
    关键决策与取舍

    把释放做成「清单化流程」而不是「在卸载钩子里写清理代码」,这是这个模块唯一真正重要的决策。写在钩子里更自然、更符合框架惯例;但它的问题是「没有一个地方能看出应该释放哪些东西」——新增一个定时器、新增一个订阅,开发者要自己记得回去补清理,而这是记不住的(我们就漏了四处)。清单化把「应该释放什么」变成一份显式的、可 review 的列表。代价是多一层间接(资源要注册到清单里),而且约定需要团队遵守缓解手段是把注册和创建绑在一起(创建资源的封装函数自动注册到当前房间的清单里),这样和 IM 那边「注册即清理」的思路一致:让机制保证而不是让规范保证。

    防抖与预建连同时做,看起来矛盾但依据不同。防抖针对的是「用户正在快速滑过」的房间——他不会停留,建连是纯浪费。预建连针对的是「用户最可能停下的下一个房间」——提前建连能缩短首帧。两者的判断依据是「用户会不会在这个房间停留」,而滑动速度和方向正好能提供这个信号。取舍是预建连会有一定的浪费(预测错了就白建),缓解手段是只预建一个、且在弱网或低端机上关闭预建连——那些场景下浪费的代价更高。

    长连接按房间换订阅而不是断开重建,这是从 IM 那边直接搬过来的判断。长连接是应用级资源而不是页面级资源,切页面不该断连接。重连的代价包括:握手、鉴权、以及重连期间的消息丢失窗口,而换订阅只是发一条信令。这一条在两个完全不同的业务里(IM 和直播)结论一致,说明它是个通用判断。

    踩过的坑:连续滑二三十个直播间后应用被系统回收。四处泄漏叠加:播放器实例、长连接订阅回调、动效队列未播完的任务、定时器单独看每一处都不大,累积几十个房间就致命了;而且开发时切三五个房间压根测不出来。修法是清单化释放加内存自检上报教训有两条泄漏问题的严重性取决于「这个流程会被重复多少次」——同样的漏释放在一个只进一次的页面里毫无影响;还有测试必须按真实使用模式做,「切三五个」和「连续切三十个」不是量的差别。

    踩过的坑二:旧房间的弹幕串进新房间。退房时没有退订旧房间的消息订阅,新房间订阅了自己的,于是两个房间的消息都进来了这个 bug 的用户感知极其明显——会被理解成「串台」这种严重的内容安全问题,客服接到的反馈是「我在这个直播间看到了别的主播的弹幕」。修法是退订纳入释放清单教训是:订阅类资源的泄漏不只是内存问题,它会造成可见的数据错乱——比纯内存泄漏更需要优先处理。

    踩过的坑三:快速滑动时对每个中间房间都建连,滑动本身变卡。用户连滑五个房间,我们对五个都创建播放器都建连,而他只在最后一个停留;更糟的是这些创建与销毁本身在制造卡顿,让滑动更不流畅——用户想快速找到感兴趣的直播间,而我们让滑动变卡了。修法是防抖,只对停下来的房间建连教训是:预加载类的优化要看「用户是否真的会用到」,对快速滑过的内容做预加载是负优化,它消耗的资源正好损害了当前正在进行的交互。

    踩过的坑四:退房时把未完成的送礼请求一起取消了。用户送礼后立刻上滑切房间,我们在退房清理时取消了未完成的请求,而服务端其实已经扣款成功,用户的礼物没送出去但钱扣了。修法是区分「要清理的资源」和「要完成的在途请求」,后者不随退房取消,并且结果要能正确归属到原房间教训是:清理资源时必须区分「纯本地资源」和「有服务端副作用的在途操作」——前者可以随手清掉,后者必须让它走完。

    没做的部分:没做多房间的预加载(同时预拉两三个房间的流以做到「秒开」)。技术上可行但同时多路拉流的带宽与解码成本很高,在移动端得不偿失,只做了单个预建连。也没做进房前的封面预览过渡(用封面图垫首帧),需要设计配合,排期没排上。

    数字是怎么测的

    连续切换的内存:最该报的数字。报连续切换指定数量的直播间后的内存曲线,改造前是持续增长最终被回收,改造后平稳。必须说明切换了多少个、什么机型——「切三五个」和「连续切三十个」结论完全不同。

    弹幕串房:确定性验证——切换房间后检查弹幕列表中不含上一个房间的消息这一项必须是常驻用例,因为它的用户感知极其明显(会被理解成串台)。

    释放清单的完整性:清单包含哪几类资源,并说明「新增资源必须加进清单」的流程约定与它是怎么被保证的(比如创建封装自动注册)。这个清单本身就是证明。

    滑动流畅度:快速连续滑动时的帧率,改造前后对比。关键是要说明改造前的卡顿来自「对中间房间的建连与销毁」——这个归因让数字有意义。

    预建连的收益与浪费:两个都要报——预建连命中时的首帧提升,以及预建连未命中(预测错)的比例主动报浪费比只报收益可信,而且说明了为什么只预建一个。

    在途请求的正确性:确定性验证——送礼后立刻切房间,检查礼物正常送达且结果归属到原房间。这是踩坑后固化的用例。

    内存自检的触发:线上内存自检超阈值的上报次数这个数字要有——如果一直是零,要么真的没问题,要么自检没生效,得说清是哪种

    不要报「零内存泄漏」。泄漏是持续对抗的,新增功能就可能引入新的泄漏——这也正是我们做内存自检上报的原因。正确表述是「连续切换 N 个房间的内存曲线变化、释放清单覆盖哪几类资源与流程约定、弹幕串房与在途请求由常驻用例保证、内存自检的线上上报」。

    面试追问
    Q:用户上下滑快速切换直播间,会有什么问题? A:三类问题,都来自「进房创建得很完整、退房释放得不完整」。一是内存持续增长:连续滑二三十个直播间后内存明显上涨,最后应用被系统回收;查下来是四处泄漏叠加——播放器实例、长连接订阅回调、动效队列未播完的任务、定时器,单独看每一处都不大,累积几十个房间就致命了;而且开发时切三五个房间压根测不出来二是旧房间弹幕串进新房间(下一题细说)。三是快速滑动时对每个中间房间都建连:连滑五个房间就对五个都创建播放器都建连,而他只在最后一个停留,中间四个是纯浪费;更糟的是这些创建与销毁本身在制造卡顿,让滑动更不流畅——用户想快速找到感兴趣的直播间,而我们让滑动变卡了。教训两条泄漏问题的严重性取决于「这个流程会被重复多少次」测试必须按真实使用模式做,「切三五个」和「连续切三十个」不是量的差别。
    Q:退房时要释放什么?写在组件卸载钩子里行不行? A:写在卸载钩子里一定会漏,我们就漏了四处——正确做法是把释放做成清单化流程。写在钩子里更自然、更符合框架惯例,但它的问题是「没有一个地方能看出应该释放哪些东西」:新增一个定时器、新增一个订阅,开发者要自己记得回去补清理,而这是记不住的清单化的意思是有一份显式的、可 review 的资源列表,退房时逐项执行:播放器停止并解绑媒体源(只停止不够——暂停的播放器仍可能持有解码器与缓冲,这和我在直播中控台踩的坑是同一个)、长连接退订该房间动效队列清空未播完的任务(否则新房间会突然播出上个房间的礼物动效)、定时器全部清理节点池归还并重置代价是多一层间接、约定需要团队遵守缓解手段是把注册和创建绑在一起——创建资源的封装函数自动注册到当前房间的清单里,这样和我在 IM 那边「注册即清理」的思路一致:让机制保证而不是让规范保证
    Q:切了房间之后看到上个直播间的弹幕,怎么回事? A:退房时没有退订旧房间的消息订阅——而这个 bug 的性质比内存泄漏严重。新房间订阅了自己的消息,而旧房间的订阅还在,于是两个房间的消息都进来了用户感知极其明显:客服接到的反馈是「我在这个直播间看到了别的主播的弹幕」,会被理解成「串台」这种严重的内容安全问题。修法是把退订纳入释放清单教训是:订阅类资源的泄漏不只是内存问题,它会造成可见的数据错乱——比纯内存泄漏更需要优先处理,而且它必须有常驻用例(切换房间后检查弹幕列表不含上一个房间的消息)。顺带说长连接的处理方式长连接按房间切换订阅,而不是断开重建。因为长连接是应用级资源而不是页面级资源,切页面不该断连接;重连的代价包括握手、鉴权、以及重连期间的消息丢失窗口,而换订阅只是发一条信令这个判断我在 IM 客户端也是同一个结论,两个完全不同的业务里结论一致,说明它是通用判断。
    Q:用户送了礼物立刻上滑切房间,礼物还算吗? A:必须算,而我们踩过这个坑——退房清理时把未完成的送礼请求一起取消了。用户送礼后立刻上滑切房间,我们在退房清理时取消了未完成的请求,而服务端其实已经扣款成功,结果是用户的礼物没送出去但钱扣了。修法是区分「要清理的资源」和「要完成的在途请求」:后者不随退房取消、要让它走完,并且结果要能正确归属到原房间(不能把礼物记到新房间去)。教训是:清理资源时必须区分「纯本地资源」和「有服务端副作用的在途操作」——前者可以随手清掉,后者必须让它走完相关的还有一个细节退房埋点(观看时长)要在释放资源之前上报,先释放再上报会拿不到数据。还有从后台返回时要判断当前房间是否还有效——主播可能已经下播了,不能直接按原状态继续。最后我会补一句度量上的做法:因为泄漏是持续对抗的、新增功能就可能引入新泄漏,我们加了连续切换后的内存自检并超阈值上报而不是声称「零内存泄漏」

没有匹配的内容,换个关键词试试。

项目拆解 · 直播观众端(uni-app)· 共 3 个模块 · 数字均为示例,需替换成自己项目的真实数据