草庐IT

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

字节跳动技术团队 2023-03-28 原文

作者 | 王海超

背景介绍

直播 OOM 问题比较棘手难以定位,主要体现在涉及的业务很多,从定位到解决花费时间比较久。为了提前触达问题,提高定位的效率,也是对现有工具的补充,提出直播内存抖动解决方案- MemoryThrashing。

为什么要提出这个方案 ?

  • 现有的 “MemoryGraph” 工具可以通过抓取的“MemoryGraph” 文件分析 OOM 成因,比如内存泄漏、内存占用过高导致的 OOM 问题,但因为性能开销很大,所以是采样上报且采样率很低,不容易触达问题, 只能定向对已知用户开启才行。期望自研一个工具,在内存增长时可以发现问题,也能用于 OOM 发生后的分析,同时具备性能开销小、全采样的能力;
  • “MemoryGraph” 生成时可能不是内存高位,比如设备内存是 4G,生成的“MemoryGraph” 可能是 1G,会影响 OOM 分析;

什么是 Thrashing(抖动) ?

维基百科中 thrashing 定义: 

  • In computer science, thrashing occurs when a computer's virtual memory resources are overused, leading to a constant state of paging and page faults, inhibiting most application-level processing.([1]) This causes the performance of the computer to degrade or collapse.

从业务视角定义内存 thrashing:

通俗讲就是性能数据有大的波动,拿内存来讲,当内存短时间从 600M 涨到 800M 叫作一个抖动。希望通过自研工具找出这 200 M 内存增长来自于哪里,在实际的 OOM 案例中因内存突增导致的 OOM 是比较常见的,具体现象如下:

  • 内存未回落:内存突增一般发生在一两分钟内,内存从 1G 涨到 3G,这部分内存会一直滞留在内存中不会被释放掉或者没有机会释放掉直接 OOM,同时助高了内存水位很容易发生 OOM 问题;
  • 内存回落:内存突增到一定水位开始回落未形成 OOM,这种现象通常是内存问题不够劣化,或者机器本身内存足够大不容易 OOM,虽然没有造成 OOM 但也是一个潜在的问题;

以临时对象、内存堆积为例来阐述如何定位该类问题,通过“AllocTime Summary” 描述临时对象分配次数,通过 “Memory Summary” 描述内存堆积。

临时对象

临时对象:短时间分配大量对象,导致直播稳定性波动较大,可能使内存、CPU 负载变高。这类问题通常表现为短时间内存冲高或者直接 OOM,或之后开始迅速回落到正常水位,这类对象不会驻留内存过久,通过监控 “临时对象” 可以提前发现这类问题。

以上是按分配次数(AllocTime Summary)统计的 TOP 临时对象,“AllocTime Summary 1” 代表第一次采样 Class 的分配次数其它依次类推。举例:通过 diff “AllocTime Summary 2” 与 “AllocTime Summary 1” 差值可知 “LivexxxA” 在采样周期分配了 7803 次,由于未采集到 “Memory Summary” 信息,可认为未有内存驻留。

内存堆积

内存堆积:内存驻留了大量对象,而且这类对象短时间不会释放掉,导致内存水位居高不下,很容易触发 OOM 问题。

以上是按内存驻留统计的 TOP 实例,“Memory Summary 1” 代表第一次采样实例数量的内存驻留信息其他依次类推。举例:通过 diff “Memory Summary 2” 与 “Memory Summary 1” 可知 “LivexxxA” 在采样周期内增长了 56791 个,根据最后一次采样可知内存驻留了总共 69904 个实例,通过采样可知“LivexxxA” 每次都是递增的。

MemoryThrashing 方案

方案调研

方案思路是做内存差值找出增长,通过采样多个时刻的内存信息(目前主要监控 Class 的实例个数), Diff 出内存信息找出 TOP 增长,达到归因的目的。

  • 内存区:通过内存节点遍历统计 Class 实例个数;
  • Runtime:通过 alloc、dealloc 计数实现统计实例存活数量;

内存区

通过内存节点遍历与已注册的 Class 比较统计实例个数,该方案的优点是可以监控整个 APP 的 OC 对象实例个数,面对直播业务场景需不需监控整个 APP 的对象,目前看暂时用不到,需求出发点是监控直播场景且满足一定条件。比如:直播观播一段时间后内存的大幅波动,场景比较聚焦。另一个考虑是如果当前内存比较大,遍历 zone 会比较耗时,如果不挂起线程会有潜在的崩溃问题、以及数据不准问题。

RunTime

通过 Hook 的方式,统计 Class 实例的分配、释放次数,达到记录实例存活个数的目的,可监控固定场景的 OC 实例增长情况,如直播间内的内存突增,范围比较小不需要统计过多的无用对象。该方案相对内存区遍历耗时小,且不会有野指针问题。但需要注意的是监控对象时对性能的影响,目前采用的是 RunTime 方案,从线下直播间测试情况看对主线程的影响忽略不计。

方案设计

在实际开发过程中发现对象的创建、释放处于复杂的多线程环境中,处理不当会对业务产生潜在的影响,影响到业务执行效率或者造成稳定性问题:

  • 容器置于多线程下会有线程安全问题;
  • 过度的使用锁会阻塞业务代码执行,也可能触发 Watchdog 机制导致 APP 被 kill;

经过优化采用多级缓存方案解决主线程的性能开销问题,达到主线程几乎零开销。

监控流程

在进入直播间一段时间后开启监控,通过监控内存值变化来区分是否开启采样功能,开启采样后会进入连续多次采样阶段,多次采样完成后进行数据上报,上报完成后会继续监控内存。

数据展示

在高热直播间多次采样的内存快照,采集 TOP 100 数据,以 “LivexxxA” 为例两次采样中第二次增长了 4125 个实例,可以简单归因 “LivexxxA” 相关业务导致 “MemoryThrashing”,可以从 “LivexxxA” 相关业务入手排查。

方案优缺点

方案

优点

缺点

“MemoryThrashing”

可以多次采样,对比内存增长趋势;性能开销小,可线上全量;提前感知内存问题;上手简单,通过对象数量就可以排查问题;

不支持多语言,只限于 oc 语言;不具备通过内存节点关系分析内存泄漏问题,只能找出堆积的对象;不具备分析多个内存区的能力;Hook 方式影响方法缓存;

“MemoryGraph”

问题发现能力强:可以通过内存节点关系分析内存泄漏导致的 OOM 问题;可以统计内存区的内存占用情况;适用多语言;上手复杂,需要梳理内存节点引用关系;

线程挂起会影响业务执行,用户感知明显;内存使用越高,内存区遍历越耗时;只能少量采样;

实践案例

目前 “MemoryThrashing” 已经部署了,可以监控测试环境,后续将部署到线上。通过线下看提前暴露了很多问题,相对以往方式只有问题发生了或者产生了明显影响才能感知到,需要 QA 反馈到 RD,通过“MemoryThrashing”大大提升了排查效率,很好的将劣化问题前置发现,以下抽取其中两个案例。

内存堆积

如下,多个采样周期内出现了大量对象的分配问题,且这些对象未释放,并且导致了内存明显上涨,采样周期 3 比采样周期 2 多分配了 234024 个对象,且最后内存驻留了 238800 个 “LivexxxBigDataRead” 对象,占用内存 10.9M。

临时对象

如下,是开播场景抓到的问题,在主播端开启弹幕狂欢时,过 Effect 认出人脸后,就会创建一个对应的轮廓模型给到中台去画轮廓,频率会很高,每 5 秒周期(实际时间更小)临时对象增量高峰可到 6w 个(后两次采样差值),由于未生成 “Memory Summary” 信息可认为未驻留内存 ,累计过百万次对象分配,对开播性能会产生直接影响:

未来规划

归因能力

只统计 OC 对象数据在某些情况下可能不够,比如公共基础对象异常增长,则没有办法追踪到具体成因,如果带有对象引用关系可以进一步锁定问题。当然这些都是对 “Memory Graph” 能力的补充,如果“Memory Graph” 已经抓到了数据,可以结合“Memory Graph” 锁定对象引用链路继而找到业务。

  • “MemoryThrashing” 可以加上对象引用关系计算,从效率上讲没必要对所有的对象查找其引用关系,查找引用关系是比较耗时的。只需查找 TOP 增长点的关键对象引用关系,实测可能只需要查找几个对象的引用关系。
  • 通过线程堆栈采样记录信息;

CPU 监控

根据以往案例如:OOM、ANR 有不少会伴随着高 CPU 使用率,比如某次案例由大量数据处理导致的 OOM 问题,经排查发现负责该业务处理的线程 CPU 使用率很高,所以通过监控线程 CPU 使用率,来补充监控显得很有必要,可以通过线程名字、堆栈, 锁定怀疑的业务。

有关MemoryThrashing:抖音直播解决内存抖动实践的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  3. ruby-on-rails - Ruby 中的内存模型 - 2

    ruby如何管理内存。例如:如果我们在执行过程中采用C程序,则以下是内存模型。类似于这个ruby如何处理内存。C:__________________|||stack|||------------------||||------------------|||||Heap|||||__________________|||data|__________________|text|__________________Ruby:? 最佳答案 Ruby中没有“内存”这样的东西。Class#allocate分配一个对象并返回该对象。这就是程序

  4. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  5. 屏幕录制为什么没声音?检查这2项,轻松解决 - 2

    相信很多人在录制视频的时候都会遇到各种各样的问题,比如录制的视频没有声音。屏幕录制为什么没声音?今天小编就和大家分享一下如何录制音画同步视频的具体操作方法。如果你有录制的视频没有声音,你可以试试这个方法。 一、检查是否打开电脑系统声音相信很多小伙伴在录制视频后会发现录制的视频没有声音,屏幕录制为什么没声音?如果当时没有打开音频录制,则录制好的视频是没有声音的。因此,建议在录制前进行检查。屏幕上没有声音,很可能是因为你的电脑系统的声音被禁止了。您只需打开电脑系统的声音,即可录制音频和图画同步视频。操作方法:步骤1:点击电脑屏幕右下侧的“小喇叭”图案,在上方的选项中,选择“声音”。 步骤2:在“声

  6. 【高数】用拉格朗日中值定理解决极限问题 - 2

    首先回顾一下拉格朗日定理的内容:函数f(x)是在闭区间[a,b]上连续、开区间(a,b)上可导的函数,那么至少存在一个,使得:通过这个表达式我们可以知道,f(x)是函数的主体,a和b可以看作是主体函数f(x)中所取的两个值。那么可以有,  也就意味着我们可以用来替换 这种替换可以用在求某些多项式差的极限中。方法: 外层函数f(x)是一致的,并且h(x)和g(x)是等价无穷小。此时,利用拉格朗日定理,将原式替换为 ,再进行求解,往往会省去复合函数求极限的很多麻烦。使用要注意:1.要先找到主体函数f(x),即外层函数必须相同。2.f(x)找到后,复合部分是等价无穷小。3.要满足作差的形式。如果是加

  7. 深度学习部署:Windows安装pycocotools报错解决方法 - 2

    深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal

  8. ruby-on-rails - Rails 中同一个类的多个关联的最佳实践? - 2

    我认为我的问题最好用一个例子来描述。假设我有一个名为“Thing”的简单模型,它有一些简单数据类型的属性。像...Thing-foo:string-goo:string-bar:int这并不难。数据库表将包含具有这三个属性的三列,我可以使用@thing.foo或@thing.bar之类的东西访问它们。但我要解决的问题是当“foo”或“goo”不再包含在简单数据类型中时会发生什么?假设foo和goo代表相同类型的对象。也就是说,它们都是“Whazit”的实例,只是数据不同。所以现在事情可能看起来像这样......Thing-bar:int但是现在有一个新的模型叫做“Whazit”,看起来

  9. ruby-on-rails - 向 Rails 3 添加 Ruby 扩展方法的最佳实践? - 2

    我有一个要在我的Rails3项目中使用的数组扩展方法。它应该住在哪里?我有一个应用程序/类,我最初把它放在(array_extensions.rb)中,在我的config/application.rb中我加载路径:config.autoload_paths+=%W(#{Rails.root}/应用程序/类)。但是,当我转到railsconsole时,未加载扩展。是否有一个预定义的位置可以放置我的Rails3扩展方法?或者,一种预先定义的方式来添加它们?我知道Rails有自己的数组扩展方法。我应该将我的添加到active_support/core_ext/array/conversion

  10. 键删除后 ruby​​ 哈希内存泄漏 - 2

    你好,我无法成功如何在散列中删除key后释放内存。当我从哈希中删除键时,内存不会释放,也不会在手动调用GC.start后释放。当从Hash中删除键并且这些对象在某处泄漏时,这是预期的行为还是GC不释放内存?如何在Ruby中删除Hash中的键并在内存中取消分配它?例子:irb(main):001:0>`ps-orss=-p#{Process.pid}`.to_i=>4748irb(main):002:0>a={}=>{}irb(main):003:0>1000000.times{|i|a[i]="test#{i}"}=>1000000irb(main):004:0>`ps-orss=-p

随机推荐