草庐IT

百度 Android 直播秒开体验优化

百度Geek说 2023-03-28 原文

作者 | 任雪龙

导读

网络直播功能作为一项互联网基本能力已经越来越重要,手机中的直播功能也越来越完善,电商直播、新闻直播、娱乐直播等多种直播类型为用户提供了丰富的直播内容。随着直播的普及,为用户提供极速、流畅的直播观看体验也越来越重要。

全文6657字,预计阅读时间17分钟。

01 背景

百度 APP 作为百度的航母级应用为用户提供了完善的移动端服务,直播也作为其中一个必要功能为用户提供内容。随着直播间架构、业务能力逐渐成熟,直播间播放指标优化也越来越重要。用户点击直播资源时,可以快速的看到直播画面是其中一个核心体验,起播速度也就成了直播间优化中的一个关键指标。

02 现状

由于包体积等原因,百度 APP 的 Android 版中直播功能使用插件方式接入,在用户真正使用直播功能时才会将直播模块加载。为解决用户点击直播功能时需要等待插件下载、安装、加载等阶段及兼容插件下载失败的情况,直播团队将播放、IM 等核心能力抽到了一个独立的体积较小的一级插件并内置在百度 APP 中,直播间的挂件、礼物、关注、点赞等业务能力在另外一个体积较大的二级插件中。特殊的插件逻辑和复杂的业务场景使得 Android 版整体起播时长指标表现的不尽人意。

2022 年 Q1 直播间整体起播时长指标 80 分位在 3s 左右,其中二跳(直播间内上下滑)场景在 1s 左右,插件拆分上线后通过观察起播数据发现随着版本收敛,一跳进入直播间携带流地址(页面启动后会使用该地址预起播,与直播列表加载同步执行)场景起播时有明显的增长,从发版本初期 1.5s 左右,随版本收敛两周内会逐步增长到 2.5s+。也就是线上在直播间外点击直播资源进直播间时有很大一部分用户在点击后还需要等待 3s 甚至更长时间才能真正看到直播画面。这个时长对用户使用直播功能有非常大的负向影响,起播时长指标急需优化。

03 目标

△起播链路

起播过程简单描述就是用户点击直播资源,打开直播页面,请求起播地址,调用内核起播,内核起播完成,内核通知业务,业务起播完成打点。从对内核起播时长监控来看,直播资源的在内核中起播耗时大约为 600-700ms,考虑链路中其他阶段损耗以及二跳(直播间内上下滑)场景可以在滑动时提前起播,整体起播时长目标定位为1.5 秒;考虑到有些进入直播间的位置已经有了起播流地址,可以在某些场景省去 “请求起播地址” 这一个阶段,在这种直播间外已经获取到起播地址场景,起播时长目标定为 1.1 秒。

04 难点

特殊的插件逻辑和复杂的业务场景使得 Android 版每一次进入直播的起播链路都不会完全一样。只有一级插件且二级插件还未就绪时在一级插件中请求直播数据并起播,一二级插件都已加载时使用二级插件请求直播数据并处理起播,进直播间携带流地址时为实现秒开在 Activity 启动后就创建播放器使用直播间外携带的流地址起播。除了这几种链路,还有一些其他情况。复杂的起播链路就导致了,虽然在起播过程中主要节点间都有时间戳打点,也有天级别相邻两个节点耗时 80 分位报表,但线上不同场景上报的起播链路无法穷举,使用现有报表无法分析直播大盘起播链路中真正耗时位置。需要建立新的监控方案,找到耗时点,才能设计针对性方案将各个耗时位置进行优化。

05 解决方案

5.1 设计新报表,定位耗时点

△一跳有起播地址时起播链路简图

由于现有报表无法满足起播链路耗时阶段定位,需要设计新的监控方案。观察在打开直播间时有流地址场景的流程图(上图),进入直播间后就会同步创建直播间列表及创建播放器预起播,当直播间列表创建完毕且播放器收到首帧通知时起播流程结束。虽然用户点击到页面 Activity 的 onCreate 中可能有多个节点(一级插件安装、加载等),页面 onCreate 调用播放器预起播中可能多个节点,内核完成到直播业务收到通知中有多个节点,导致整个起播链路无法穷举。但是我们可以发现,从用户点击到 onCreate 这个路径是肯定会有的,onCreate 到创建播放器路径也是肯定有的。这样就说明虽然两个关键节点间的节点数量和链路无法确定,但是两个关键节点的先后顺序是一定的,也是必定会有的。由此,我们可以设计一个自定义链路起点和自定义链路终点的查询报表,通过终点和起点时间戳求差得到两个任意节点间耗时,将线上这两个节点所有差值求 80 分位,就可以得到线上起播耗时中这两个节点间耗时。将起播链路中所有核心关键节点计算耗时,就可以找到整个起播链路中有异常耗时的分段。

按照上面的思路开发新报表后,上面的链路各阶段耗时也就比较清晰了,见下图,这样我们就可以针对不同阶段逐个击破。

△关键节点间耗时

5.2 一跳使用一级插件起播

使用新报表统计的重点节点间耗时观察到,直播间列表创建(模版组件创建)到真正调用起播(业务视图就绪)中间耗时较长,且这个耗时随着版本收敛会逐步增加,两周内大约增加 1000ms,首先我们解决这两个节点间耗时增加问题。

经过起播链路观察和分析后,发现随版本收敛,这部分起播链路有较大变化,主要是因为随版本收敛,在二级插件中触发 “业务调用起播” 这个节点的占比增加。版本收敛期,进入直播间时大概率二级插件还未下载就绪或未安装,此时一级插件中可以很快的进行列表创建并创建业务视图,一级插件中在 RecyclerView 的 item attach 到视图树时就会触发起播,这个链路主要是等待内核完成首帧数据的拉取和解析。当二级插件逐渐收敛,进入直播间后一级插件就不再创建业务视图,而是有二级插件创建业务视图。由于二级插件中业务组件较多逐个加载需要耗时还有一级到二级中逐层调用或事件分发也存在一定耗时,这样二级插件起播场景就大大增加了直播间列表创建(模版组件创建)到真正调用起播(业务视图就绪)中间耗时。

5.2.1 一跳全部使用一级插件起播

基于上面的问题分析,我们修改了一跳场景起播逻辑,一跳全部使用一级插件起播。一级插件和二级插件创建的播放器父容器 id 是相同的,这样在一级插件中初始化播放器父容器后,当内核首帧回调时起播过程就可以结束了。二级插件中在初始化播放器父容器时也会通过 id 判断是否已经添加到视图树,只有在未添加的情况(二跳场景或一跳时出现异常)才会在二级中进行兜底处理。在一级插件中处理时速度可以更快,一级优先二级兜底逻辑保证了进入直播间后一定可以顺利初始化视图。

5.2.2 提前请求接口

使用由一起插件处理起播优化了二级插件链路层级较多问题,还有一个耗时点就是进直播间时只传入了房间 room\_id 未携带流地址场景,此时需要通过接口请求获取起播数据后才能创建播放器和起播。为优化这部分耗时,我们设计了一个直播间数据请求管理器,提供了缓存数据和超时清理逻辑。在页面 onCreate 时就会触发管理器进行接口请求,直播间模版创建完成后会通过管理器获取已经请求到的直播数据,如果管理器接口请求还未结束,则会复用进行中请求,待请求结束后立刻返回数据。这样在进直播间未携带流数据时我们可以充分利用图中这 300ms 时间做更多必要的逻辑。

5.3 播放器Activity外预起播

通过进直播间播放器预创建、预起播、一跳使用一级插件起播等方案来优化进入直播间业务链路耗时后,业务链路耗时逐渐低于内核部分耗时,播放器内核耗时逐渐成为一跳起播耗时优化瓶颈。除了在内核内部探索优化方案,继续优化业务整个起播链路也是一个重要方向。通过节点间耗时可以发现,用户点击到 Activity 页面 onCrete 中间也是有 300ms 左右耗时的。当无法将这部分耗时缩到更短时,我们可以尝试在这段时间并行处理一些事情,减少页面启动后的部分逻辑。

一级插件在百度 APP 中内置后,设计并上线了插件预加载功能,上线后用户通过点击直播资源进入直播间的场景中,有 99%+ 占比都是直播一级插件已加载情况,一级插件加载这里就没有了更多可以的操作空间。但将预起播时机提前到用户点击处,可以将内核数据加载和直播间启动更大程度并行,这样来降低内核耗时对整个起播耗时影响。

△播放器在直播间外起播示意图

如上图,新增一个提前起播模块,在用户点击后与页面启动并行创建播放器起播并缓存,页面启动后创建播放器时会先从提前起播模块的缓存中尝试取已起播播放器,如果未获取到则走正常播放器创建起播逻辑,如果获取到缓存的播放器且播放器未发生错误,则只需要等待内核首帧即可。

播放器提前起播后首帧事件大概率在 Activity 启动后到达,但仍有几率会早于直播业务中设置首帧监听前到达,所以在直播间中使用复用内核的播放器时需要判断是否起播成功,如果已经起播成功需要马上分发已起播成功事件(含义区别于首帧事件,防止与首帧事件混淆)。

提前起播模块中还设计了超时回收逻辑,如果提前起播失败或 5s (暂定)内没有被业务复用(Activity 启动异常或其他业务异常),则主动回收缓存的播放器,防止直播间没有复用成功时提前创建的播放器占用较多内存及避免泄漏;超时时间是根据线上大盘起播时间决定,使用一个较大盘起播时间 80 分位稍高的值,防止起播还未完成时被回收,但也不能设置较长,防止不会被复用时内存占用较多。

通过提前起播功能,实验期命中提前起播逻辑较不进行提前起播逻辑,整体起播耗时 80 分位优化均值:450ms+。

5.4直播间任务打散

△内核首帧分发耗时

业务链路和内核链路耗时都有一定优化后,我们继续拆解重点节点间耗时。内核内部标记首帧通知到直播业务真正收到首帧通知之间耗时较长,如上图,线上内核首帧分发耗时 80 分位均值超过 1s,该分段对整体起播耗时优化影响较大。内核首帧是在子线程进行标记,通知业务时会通过主线程 Handler 分发消息,通过系统的消息分发机制将事件转到主线程。

通过排查内核标记首帧时间点到业务收到首帧通知事件时间点之间所有主线程任务,发现在首帧分发任务开始排队时,主线程任务队列中已有较多其他任务,其他事件处理时间较长,导致首帧分发排队时间较久,分发任务整体耗时也就较长。直播业务复杂度较高,如果内核首帧分发任务排队时直播间其他任务已在队列中或正在执行,首帧分发任务需要等直播任务执行完成后才能执行。

通过将直播间启动过程中所有主线程任务进行筛查,发现二级插件的中业务功能较多,整体加载任务执行时间较长,为验证线上也是由于二级业务任务阻塞了首帧分发任务,我们设计了一个二级组件加载需要等待内核首帧后才能进行的实验,通过实验组与对照组数据对比,在命中实验时首帧分发耗时和起播整体耗时全部都有明显下降,整体耗时有 500ms 左右优化。

通过实验验证及本地对起播阶段业务逻辑分析,定位到直播间各业务组件及对应视图的预加载数量较多且耗时比较明显,这个功能是二级插件为充分利用直播间接口数据返回前时间,二级插件加载后会与接口请求并行提前创建业务视图,提起初始化组件及视图为接口完成后组件渲染节省时间。如果不预创建,接口数据回来后初始化业务组件也会主动创建后设置数据。但将所有预创建任务全部串行执行耗时较长,会阻塞主线程,页面一帧中执行太多任务,也会造成页面明显卡顿。

发现这个阻塞问题后,我们设计了将预创建视图任务进行拆分打散,将一起执行的大任务拆分成多个小任务,每个组件的初始化都作为一个单独任务在主线程任务队列中进行排队等待执行。避免了一个大任务耗时特别长的问题。该功能上线后,整个二级插件中的组件加载大任务耗时降低了 40%+。

5.5 内核子线程分发首帧

由于主线程消息队列中任务是排队执行的,将阻塞首帧分发事件的大任务拆分成较多小任务后,还是无法解决首帧事件开始排队时这些小任务已经在主线程任务队列中排队问题。除了降低直播业务影响,还可以通过加快内核任务分发速度,使首帧分发耗时降低。需要设计一个在不影响内核稳定性与业务逻辑情况下内核首帧事件如何避免主线程排队或快速排队后被执行的方案。

为解决上面的问题, 我们推动内核,单独增加了一个子线程通知业务首帧事件能力。业务收到子线程中首帧回调后通过 Handler 的 postAtFrontOfQueue() 方法将一个新任务插到主线程任务队列最前面,这样主线程处理完当前任务后就可以马上处理我们新建的这个任务,在这个新任务中可以马上处理播放器上屏逻辑。无需等待播放内核原本的主线程消息。

主线程任务前插无法打断新任务排队时主线程中已经开始执行的任务,需要正在执行任务结束后才会被执行。为优化这个场景,内核通过子线程通知首帧后,播放器中需要记录这个状态,在一级插件及二级插件中的直播间业务任务执行开始前后,增加判断播放器中是否已经收到首帧逻辑,如果已经收到,就可以先处理上屏后再继续当前任务。

通过直播内核首帧消息在主线程任务队列前插和业务关键节点增加是否可上屏判断,就可以较快处理首帧通知,降低首帧分发对起播时长影响。

5.6 起播与完载指标平衡

直播间起播优化过程中,完载时长指标(完载时长:用户点击到直播间核心功能全部出现的时间,其中经历页面启动,直播间列表创建,二级插件下载、安装、加载,直播间接口数据请求,初始化直播间功能组件视图及渲染数据,核心业务组件显示等阶段)的优化也在持续进行。直播间二级插件是在使用二级插件中的功能时才会触发下载安装及加载逻辑,完载链路中也注意到了用户点击到页面 onCreate 这段耗时,见下图。

△页面启动耗时示意图

为优化直播间完载指标,直播团队考虑如果将插件加载与页面启动并行,那么完载耗时也会有一定的优化。直播团队继续设计了二级插件预加载方案,将二级插件加载位置提前到了用户点击的时候(该功能上线在 5.4、5.5 章节对应功能前)。该功能上线后试验组与对照组数据显示,实验组完载耗时较对照组确实有 300ms+ 优化。但起播耗时却出现了异常,实验组的起播耗时明显比对照组增长了 500ms+,且随版本收敛这个起播劣化还在增加。我们马上很快发现了这个异常,并通过数据分析确定了这个数据是正确的。完载的优化时如何引起起播变化的?

经过数据分析,我们发现起播受影响的主要位置还是内核首帧消息分发到主线程这个分段引起,也就是二级插件加载越早,内核首帧分发与二级组件加载时的耗时任务冲突可能性越大。确认问题原因后,我们做了 5.4、5.5 章节的功能来降低二级组件加载任务对起播影响。由于二级插件中的耗时任务完全拆分打散来缓解二级插件预下载带来的起播劣化方案复杂度较高,对直播间逻辑侵入太大,二级插件提前加载没有完全上线,完载的优化我们设计了其他方案来实现目标。

虽然不能在进入直播间时直接加载二级插件,但我们可以在进入直播间前尽量将二级插件下载下来,使用时直接加载即可,这个耗时相对下载耗时是非常小的。我们优化了插件预下载模块,在直播间外展示直播资源时触发该模块预下载插件。该模块会通过对当前设备网络、带宽、下载频次等条件综合判断,在合适的时机将匹配的二级插件进行下载,插件提前下载后对完载指标有较大优化。除了插件预下载,直播间内通过 5.4 章节直播间二级组件初始化拆分,也将全部组件初始化对主线程阻塞进行了优化,这样接口数据请求成功后可以优先处理影响完载统计的组件,其他组件可以在完载结束后再进行初始化,这个方案也对直播完载指标有明显优化。

除了以上两个优化方案,直播团队还在其他多个方向对完载指标进行了优化,同时也处理了完载时长与起播时长的指标平衡,没有因为一个指标优化而对其他指标造成劣化影响。最终实现了起播、完载指标全部达到目标。

06 收益

△2022 Android 端起播耗时走势

经过以上多个优化方案逐步迭代,目前 Android 端最新版本数据,大盘起播时间已经由 3s+ 降到 1.3s 左右;一跳带流地址时起播时长由 2.5s+ 左右降低到 1s 以内;二跳起播时长由 1s+ 降低到 700ms 以内,成功完成了预定目标。

07 展望

起播时长作为直播功能一个核心指标,还需要不断打磨和优化。除了业务架构上的优化,还有优化拉流协议、优化缓冲配置、自适应网速起播、优化 gop 配置、边缘节点加速等多个方向可以探索。百度直播团队也会持续深耕直播技术,为用户带来越来越好的直播体验。

——END——

推荐阅读:

​iOS SIGKILL 信号量崩溃抓取以及优化实践​

​如何在几百万qps的网关服务中实现灵活调度策略​

​深入浅出DDD编程​

​百度APP iOS端内存优化实践-内存管控方案​

​Ernie-SimCSE对比学习在内容反作弊上应用​

​质量评估模型助力风险决策水平提升​

有关百度 Android 直播秒开体验优化的更多相关文章

  1. 安卓apk修改(Android反编译apk) - 2

    最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路

  2. 西安华为OD面试体验 - 2

    西安华为OD面试体验开始投简历技术面试进展工作进展开始投简历去年一整年一直在考研和工作之间纠结,感觉自己的状态好像当时的疫情一样差劲。之前刚毕业的时候投了个大厂的简历,结果一面写算法的时候太拉跨了,虽然知道时dfs但是代码熟练度不够,放在平时给足时间自己可以调试通过,但是熟练度不够那面试当时就写不出来被刷了。说真的算法学到后期我感觉最重要的是熟练度和背板子(对于我这种普通玩家来说),面试题如果一上来短时间内想不出思路就完蛋了。然后由于当时找的工作不是很理想就又想考研了。但是考研是有风险的,我自我感觉自己可能冲不上那个学校,而找工作一个没成可以继续找嘛。本着抱着试试看的态度在boss上投了简历,

  3. Ruby 缺少常量表达式优化? - 2

    我希望Ruby的解析器会进行这种微不足道的优化,但似乎并没有(谈到YARV实现,Ruby1.9.x、2.0.0):require'benchmark'deffib1a,b=0,1whileb由于这两种方法除了在第二种方法中使用预定义常量而不是常量表达式外是相同的,因此Ruby解释器似乎在每个循环中一次又一次地计算幂常数。是否有一些Material说明为什么Ruby根本不进行这种基本优化或只在某些特定情况下进行? 最佳答案 很抱歉给出了另一个答案,但我不想删除或编辑我之前的答案,因为它下面有有趣的讨论。正如JörgWMittag所说,

  4. ruby-on-rails - 优化读取数据库和写入csv文件 - 2

    我正在尝试从数据库中读取大量单元格(超过100.000个)并将它们写入VPSUbuntu服务器上的csv文件。碰巧服务器没有足够的内存。我正在考虑一次读取5000行并将它们写入文件,然后再读取5000行,等等。我应该如何重构我当前的代码以使内存不会被完全消耗?这是我的代码:defwrite_rows(emails)File.open(file_path,"w+")do|f|f该函数由sidekiqworker调用:write_rows(user.emails)感谢您的帮助! 最佳答案 这里的问题是,当您调用emails.each时,

  5. 软约束、硬约束、Minimum Snap的轨迹优化方法 - 2

    文章目录前言约束硬约束的轨迹优化Corridor-BasedTrajectoryOptimizationBezierCurveOptimizationOtherOptions软约束的轨迹优化Distance-BasedTrajectoryOptimization优化方法前言可以看看我的这几篇Blog1,Blog2,Blog3。上次基于MinimumSnap的轨迹生成,有许多优点,比如:轨迹让机器人可以在某个时间点抵达某个航点。任何一个时刻,都能数学上求出期望的机器人的位置、速度、加速度、导数。MinimumSnap可以把问题转换为凸优化问题。缺点:MnimumSnap可以控制轨迹一定经过中间的

  6. ruby-on-rails - 负载测试期间 Unicorn CPU 使用率激增,优化方法 - 2

    我对为我的RubyonRails3.1.3应用优化我的Unicorn设置的方法很感兴趣。我目前正在高CPU超大实例上生成14个工作进程,因为我的应用程序在负载测试期间似乎受CPU限制。在模拟负载测试中,每秒大约20个请求重放请求,我的实例上的所有8个内核都达到峰值,盒子负载飙升至7-8个。每个unicorn实例使用大约56-60%的CPU。我很好奇可以通过哪些方式对其进行优化?我希望能够每秒将更多请求汇集到这种大小的实例上。内存和所有其他I/O一样完全正常。在我的测试过程中,CPU越来越低。 最佳答案 如果您受CPU限制,您希望使用

  7. 美团外卖搜索基于Elasticsearch的优化实践 - 2

    美团外卖搜索工程团队在Elasticsearch的优化实践中,基于Location-BasedService(LBS)业务场景对Elasticsearch的查询性能进行优化。该优化基于Run-LengthEncoding(RLE)设计了一款高效的倒排索引结构,使检索耗时(TP99)降低了84%。本文从问题分析、技术选型、优化方案等方面进行阐述,并给出最终灰度验证的结论。1.前言最近十年,Elasticsearch已经成为了最受欢迎的开源检索引擎,其作为离线数仓、近线检索、B端检索的经典基建,已沉淀了大量的实践案例及优化总结。然而在高并发、高可用、大数据量的C端场景,目前可参考的资料并不多。因此

  8. 「想体验ChatGPT中文聊天?」那快进来,你用不上算我输 - 2

    ♥️作者:白日参商🤵‍♂️个人主页:白日参商主页♥️坚持分析平时学习到的项目以及学习到的软件开发知识,和大家一起努力呀!!!🎈🎈加油!加油!加油!加油🎈欢迎评论💬点赞👍🏻收藏📂加关注+!「想体验ChatGPT中文聊天?」那快进来,你用不上算我输项目场景:项目条件一、那就开始吧1、安装ChatGPT-Desktop2、OpenAPI设置二、使用实例恭喜你!!!配置成功了!!!API和URL都是博主免费提供给大家的!!!恭喜你!!!配置成功了!!!API和URL都是博主免费提供给大家的!!!🎈🎈加油!加油!加油!加油🎈欢迎评论💬点赞👍🏻收藏📂加关注+!项目场景:近几个月可以说ChatGPT是火得一

  9. 基于RTS超低延时直播优化强互动场景体验 - 2

    RTS在阿里云视频直播的基础上进行底层技术优化,通过集成阿里云播放器SDK,支持在千万级并发场景下节点间毫秒级延时直播的能力,弥补了传统直播存在3~6秒延时的问题,确保了超低延时、低卡顿、秒开流畅的直播观看体验。本文介绍了基于RTS超低延迟直播优化强互动场景体验的最佳实践方案,并以阿里云播放器Aliplayer为例,详细介绍RTS超低延迟拉流接入、自动降级、排障信息获取等逻辑的实现,助力企业打造互动直播行业的产品竞争力。适用场景该方案适用于对超低延迟直播有诉求的客户,尤其是业务中存在强互动场景直播的场景。强互动场景直播主要是指对主播和观众存在互动,或观众存在更高实时性观看、画面互动需求的情况,

  10. ruby - 无法使用 CONSTANT 优化字符串 - 2

    我目前正在研究Ruby2.1.1的改进,但遇到了一些奇怪的事情。我正在尝试改进String类并定义一个名为FOO的常量。沙箱.rbmoduleFoobarrefineStringdoFOO="BAR"deffoobar"foobar"endendendusingFoobarputs"".class::FOO#=>uninitializedconstantString::FOO(NameError)puts"".foobar#=>"foobar"这给了我未初始化的常量String::FOO(NameError)。但是我可以调用"".foobar这让我相信我在正确的范围内。奇怪的是,如果我

随机推荐