当前位置: 首页 > 科技观察

MemoryThrashing:抖音解决直播中的内存抖动实践

时间:2023-03-21 18:54:55 科技观察

作者|王海潮背景介绍直播的OOM问题很难定位,主要是涉及的业务比较多,从定位到解决需要很长时间。为了提前到达问题,提高定位效率,同时也是对现有工具的补充,提出了一种实时的内存抖动解决方案——MemoryThrashing。为什么这个提议?现有的“MemoryGraph”工具可以通过抓取的“MemoryGraph”文件来分析OOM的原因,如内存泄漏、内存使用过多导致的OOM问题,但由于性能开销大,所以通过采样和采样率来报告很低不容易到达的问题,只能对已知用户开启。期望开发一个自研工具,可以在内存增长时发现问题,也可以用于OOM发生后的分析。同时具有低性能开销和全采样能力;“MemoryGraph”在生成时可能不是高层内存,比如设备内存是4G,生成的“MemoryGraph”可能是1G,会影响OOM分析;什么是抖动(抖动)?Wikipedia中对抖动的定义:在计算机科学中,抖动发生在计算机的虚拟内存资源被过度使用时,导致分页和页面错误的持续状态,抑制大多数应用程序级处理。([1])这会导致性能计算机降级或崩溃。从业务角度定义内存抖动:一般来说就是性能数据出现较大的波动。以内存为例,当内存在短时间内从600M增加到800M时,称为抖动。希望通过自研工具找出这200M的内存增长是从哪里来的。在实际的OOM案例中,内存突然增加导致的OOM是比较常见的。几分钟内,内存从1G增加到3G。这部分内存会留在内存中,不会被释放或者没有机会释放直接OOM。同时会增加内存水位,容易出现OOM问题;memorydrop:内存突增当某个水位开始下降,没有OOM形成时,这种现象一般是内存问题还不够恶化,或者机器本身内存足够大,可以防止OOM。虽然不会导致OOM,但也是一个潜在的问题;以临时对象和内存积累为例来说明如何定位这类问题,用“AllocTimeSummary”来描述临时对象分配的次数,用“MemorySummary”来描述内存积累。Temporaryobjects临时对象:短时间内分配大量对象,导致直播稳定性波动较大,可能会增加内存和CPU负载。这类问题通常表现为短期内存飙升或直接OOM,或者之后迅速回落到正常水位。这类对象不会在内存中停留太久,通过监控“临时对象”可以提前发现此类问题。以上是按分配次数(AllocTimeSummary)统计的TOP临时对象,“AllocTimeSummary1”表示第一个采样Class的分配次数,以此类推。例子:从“AllocTimeSummary2”和“AllocTimeSummary1”的区别可知,“LivexxxA”在采样周期内分配了7803次。由于没有收集到“MemorySummary”信息,可以认为没有内存常驻。内存堆积内存堆积:大量对象驻留在内存中,此类对象短时间内得不到释放,导致内存水平过高,极易引发OOM问题。以上是按内存驻留统计的TOP实例,“MemorySummary1”表示第一次采样的实例数的内存驻留信息,依次类推。例子:通过diff“MemorySummary2”和“MemorySummary1”可以知道“LivexxxA”在采样周期内增加了56791。根据上次的采样,可以知道内存中总共驻留了69904个实例。通过采样可以知道“LivexxxA”每次都是增量的。MemoryThrashing程序研究程序的思路是通过做内存差异来找出增长。通过对内存信息进行多次采样(目前主要监控Class实例数),对内存信息进行Diff,找出TOP增长情况,从而达到归因的目的。内存区:通过内存节点遍历统计Class实例的个数;运行时:通过alloc和dealloc计数,实现存活实例数的统计;内存区域通过内存节点遍历,与注册的Class实例进行比较,统计实例数量。这种方案的好处是可以监控整个APP的OC对象实例数,面对直播业务场景是否需要监控整个APP的对象,目前还没有。需求出发点是监控直播场景,满足一定的条件。例如:现场观看一段时间后,记忆波动较大,场景更加聚焦。另外一个考虑是如果当前内存比较大,遍历zone会比较费时间。如果线程没有挂起,就会有潜在的崩溃和数据不准确的情况。Runtime通过Hook的方式统计Class实例的分配和释放次数,从而记录存活实例数。可以监控固定场景下OC实例的增长情况,比如直播间内存突然增加。范围比较小,不需要统计。很多没用的东西。这种方案比内存区遍历耗时少,而且不存在野指针问题。但需要注意的是,目前使用监控对象时对性能的影响。采用RunTime方案,从线下直播间的测试情况来看,对主线程的影响可以忽略不计。在方案设计的实际开发过程中,发现对象的创建和释放是在一个复杂的多线程环境中进行的。处理不当会对业务产生潜在的影响,影响业务执行效率或造成稳定性问题:容器会被置于多线程下。线程安全问题;过度使用锁会阻塞业务代码的执行,还可能触发Watchdog机制杀死APP;优化后采用多级缓存方案解决主线程的性能开销,实现了主线程几乎为零的开销。监听进程进入直播间一段时间后开始监听,通过监听内存值的变化来区分是否开启采样功能。启用采样后,将进入连续多次采样阶段。多次采样完成后上报数据,上报完成后继续监测。记忆。数据为高热直播间多次采样的内存快照,采集TOP100数据。以“LivexxxA”为例,两次采样中第二次增加了4125个实例,可以简单归结为“LivexxxA”相关业务引起的“MemoryThrashing”,可以从“LivexxxA”开始查看相关业务。方案优缺点方案优缺点“MemoryThrashing”可以多次采样,比较内存增长趋势;性能开销小,可全量在线;可以提前感知内存问题;容易上手,可以通过对象的数量来检查问题;限于oc语言;不具备通过内存节点关系分析内存泄漏的能力,只能查找堆积的对象;不具备分析多个内存区域的能力;Hook方法影响方法缓存;内存泄漏导致的OOM问题的节点关系分析;可以统计内存区域的内存使用情况;适用于多种语言;上手比较复杂,需要梳理内存节点的引用关系;内存区域遍历比较耗时;只能取样少量样品;实际案例目前“MemoryThrashing”已经部署,可以监控测试环境,稍后会部署上线。很多问题都是通过线下观看提前暴露出来的。与以前的方法相比,只有当问题发生或产生重大影响时,才能感知到。QA需要反馈给RD。通过“MemoryThrashing”,大大提高了排查效率,很好的解决了退化问题。发现下面摘录了其中两个案例。内存积累如下。在多个采样周期分配了大量的对象,而这些对象还没有被释放,导致内存明显增加。在采样周期3中,比采样周期2多分配了234,024个对象,内存驻留在最后。238800“LivexxxBigDataRead”对象占用内存10.9M。临时对象如下,是广播场景抓到的问题。当主播开启弹幕狂欢时,通过Effect识别出人脸后,会为中台创建相应的轮廓模型来绘制轮廓,使用频率会非常高。每5秒周期(实际时间更短)临时对象增量的峰值可以达到6w(最后两个样本之间的差异)。由于没有生成“MemorySummary”信息,可以认为是不常驻内存,累计对象分配数超过百万,将直接影响广播性能:在未来的规划归因能力中,只有统计OC对象数据在某些情况下可能还不够,比如公共基础对象异常增长,无法追溯具体原因。如果存在对象引用关系,则可以进一步锁定问题。当然,这些都是对“MemoryGraph”能力的补充。如果“MemoryGraph”已经抓取到数据,可以结合“MemoryGraph”锁定对象引用链接,进而找到业务。“MemoryThrashing”可以添加到对象引用关系的计算中。从效率上来说,不需要找到所有对象的引用关系,找到引用关系比较耗时。只需要找到TOP增长点的关键对象引用关系,实测可能只需要找到少数几个对象的引用关系即可。通过线程栈采样记录信息;CPU监控根据以往案例如:OOM、ANR,很多都会伴随CPU占用率过高,比如某一个案例处理大量数据导致的OOM问题。CPU占用率很高,需要通过监控线程的CPU占用率来补充监控。可以通过线程名和堆栈锁定可疑业务。