人物采访 / T SALON

编辑内容

丰亚东谈 APM:从问题发现、根因定位到团队建设与技术成长

回顾 2022 年 T Chat 第 5 期全部三段录播,覆盖 APM 在研发流程中的作用、防劣化、疑难问题与止损工具,以及丰亚东对职业成长、专家能力、自研平台落地和团队建设的思考。

T Chat 第 5 期丰亚东分享 APM 与技术成长的访谈封面
T Chat 第 5 期丰亚东分享 APM 与技术成长的访谈封面

本期视频发布于 2022 年。文中的技术状态、个人经历与观点均为当时情况。

一次启动优化完成后,怎样避免几个版本之后又变慢?看到崩溃堆栈,为什么仍然找不到真正的原因?一个团队自研了监控平台,又怎样让已经使用其他工具的业务愿意接入?

2022 年 6 月发布的 T Chat 第 5 期,丰亚东用这些具体问题展开 APM 的工作。他在当期介绍中回顾,自己于 2016 年加入字节,2017 年底开始专注 APM 方向,参与相关平台从早期到持续扩展的建设。录播包含约 40 分钟的技术分享,以及约 32 分钟、27 分钟的两段对谈,后半场进一步讨论个人成长与平台团队如何落地工作。

技术分享:APM 要进入研发的整个过程

崩溃监控和启动优化,只是容易被看见的部分

丰亚东先从普通开发者常接触的任务讲起:新版本产生崩溃,收到问题链接后修复;随着业务增加,应用启动越来越慢,需要集中做一次优化。这些工作都属于 APM,但不足以概括这一领域。

他提出,APM 应贯穿编码、测试、集成、灰度、上线和后续优化。不同阶段面对的问题不同:上线前尽量暴露风险,灰度和线上及时发现异常、定位原因并降低影响,修复后再回到流程中补齐遗漏的检查。最终目标是帮助开发者解决问题,并改善用户体验。

上线之前,让问题尽可能早地被看见

丰亚东把每一段流程和一种实际动作配在一起。写 Objective-C 或 Swift 时可以先让 Lint 指出部分高风险写法;在本机调试时,用内存检查工具找野指针、越界和泄漏,再用性能分析工具看耗时;进入测试阶段,再把随机稳定性、设备兼容、功能、性能和单元测试接进来。他强调的是尽量在成本较低的阶段暴露问题,而不是声称运行一遍工具就能保证发布安全。

在研发阶段,他列举静态检查、内存问题检测和性能分析等工具,让开发者在写代码和调试时就能获得反馈。进入测试与集成阶段,再通过稳定性、兼容性、功能、性能和单元测试等方式覆盖更多场景。

这些手段可以互相补充,但没有哪一项检查能够保证新代码没有风险。业务快速变化、设备与运行条件不同,都会使某些问题未在测试中出现;服务端下发数据的变化,也可能在客户端版本没有更新时触发异常。因此,上线前的检查与上线后的观察缺一不可。

在线上,把发现、归因与责任人连接起来

他用服务端变更举例:客户端代码本身没有发新版,但接口忽然下发不兼容的数据,也可能导致崩溃。平台此时要先发现某类问题的数量抬升,再看同一时段有无接口或实验变化;如果 App 已按模块分工,还要将告警送到能判断该模块的同学手上。这几步若只能靠人挨个查看邮件、询问负责人,问题就会在转交中耗时。

灰度和生产环境中的重点,是尽快识别需要处理的问题。丰亚东讨论了崩溃、内存不足相关退出、卡死,以及启动、CPU、帧率、网络和资源使用等观察方向。指标出现明显变化后,平台还需要把问题送到能够处理它的人面前。

在组件化开发中,这不仅是发出一条告警,还包括判断异常与哪个模块、哪次改动或哪项服务有关,并把问题分发给相应负责人。理想状态是减少人工转交和反复询问,使发现问题之后的工作能够连续进行。

对于单靠堆栈难以解释的情况,他强调提前留下必要的日志与事件信息。问题发生后,结合某个用户或同类场景的记录寻找共同点,比只有一条孤立错误更有机会还原过程。这也要求研发时就考虑需要哪些信息,而不是出问题后才发现关键环节从未记录。

临时止损之后,还要完成修复与复盘

客户端的原生代码通常不能像服务端部署那样立即统一替换。丰亚东因此把动态降级、兜底和回退能力放进 APM 的工作范围,讨论在已经具备相关能力的前提下,怎样尽快减少线上问题的影响。

但应急处理不是终点。需要随版本完成的修复仍要推进;严重问题还要复盘,形成改进措施,并检查这些措施是否真正进入测试、工具或研发流程。否则,同类问题可能在下一轮迭代中重现。

这构成他所理解的完整过程:从发现与响应走到根因修复,再用已知问题改进前面的检查。分享中提到的各种动态处理方式,是当时的工程实践讨论,不能据此承诺任意客户端问题都能不发版修复。

三类难题:防劣化、根因与紧急响应

一次优化,怎样变成可持续的改进

他让听众想象一次启动优化:当前版本快了约半秒,接下来每个新需求又往启动路径加一点工作,数个版本后优化成果可能被完全抵消。这个例子的关键并非半秒这一数字,而是单次项目完成后若没人持续检查,性能会随迭代重新劣化。他给出的措施也按成本递进:先建立开发者自测习惯,再让自动化减少重复劳动,最后把高风险检查变成合入和发布的准入条件。

第一个难题是防劣化。某次优化能减少启动时间,但后续需求继续增加,如果没有持续检查,收益可能很快被抵消。丰亚东因此将注意力从单次优化任务,转向研发流程能否及时识别新引入的开销。

他先谈团队的质量意识:开发者应了解常用分析工具,愿意在提交代码前检查自己的改动。随后又指出,仅依靠每个人主动执行很难长期稳定,尤其在业务压力较大时。因此,应尽可能让自动化测试承担重复工作,降低持续执行的成本。

第三步是把必要检查接入代码合并和版本交付流程。例如,提交改动时运行静态检查,封版后检查整体性能与稳定性;已经确认属于阻断性的问题,需要在进入后续阶段之前处理。他将这种准入、准出机制视为约束劣化的一种办法。

这些措施仍需要合理的规则与判定条件。丰亚东用缺陷比例和各阶段修复成本的图解释“越晚发现越难处理”,图中的百分比和倍数不能当作所有团队都适用的统计事实。

堆栈告诉你在哪里出错,未必告诉你为什么

分享里的两张材料很能说明这种落差。一张系统函数占满的崩溃堆栈,无法直接指向业务改动;另一张系统内存日志只说明应用因占用过高退出,没有回答哪类对象占了空间。丰亚东因此拆开内存归因:分配调用栈回答“谁把内存分配出来”,内存图中的对象和引用链回答“为什么到现在还留着”。若一个问题只拿到其中一类线索,仍可能找不到真正应修改的持有关系。

第二个难题是疑难问题的归因。一条看起来全是系统函数的崩溃堆栈,或者一份说明内存压力的系统日志,可能让开发者知道发生了什么,却仍不知道应该修改哪段业务代码。

丰亚东以 OOM 相关问题为例,介绍两种进一步观察的方向。一种记录内存分配与调用栈的关系,寻找占用较大的来源;另一种分析内存中的对象、数量和引用关系,理解为什么这些对象仍然存在。它们提供不同角度的信息,可以帮助把宽泛的内存问题缩小到具体原因。

他在分享中提到团队的线上内存图分析实践,并将其与开发者熟悉的调试体验作类比。其工程价值在于补足归因信息,而不是证明所有内存问题都能够被某个工具完整捕捉。

尽量接近错误发生的第一现场

他的 A、B 业务例子把“第一现场”讲得很清楚:A 越界写入 B 的对象内存,A 当下继续运行;后来 B 调用对象方法时才崩溃,最终堆栈全落在 B 身上。若诊断能力能在 A 写坏内存的瞬间报告调用栈,就能避免让排查人员只围着 B 的代码转。AddressSanitizer 为线下提供了类似思路,但他指出插桩及性能成本使其不能原样承担线上监控,于是介绍团队为线上问题另做的能力。

有些崩溃发生的位置只是受影响的一方。丰亚东举例说明:某段代码越界写入,破坏了另一处对象的内存,但当时没有立即崩溃;稍后对象被使用,问题才显现。此时只看最终堆栈,容易把排查重点放错位置。

因此,他希望通过检测机制,让错误尽量在发生时暴露,并保留当时的执行信息。他介绍了 AddressSanitizer 的思路及将类似诊断目标带到线上场景时遇到的开销与实现问题,也描述团队针对这一需求建设的能力。

这里应区分目标与具体实现:让非法访问更早暴露,并不等于把开发工具原样放进生产环境。丰亚东没有展开线上方案的完整实现,也没有给出覆盖率、性能开销或检测范围的通用保证。

不崩溃的问题,也需要能还原过程的信息

UI 错乱、视频画面异常、音频问题等,不一定留下可以直接解释原因的崩溃记录。丰亚东再次强调日志:接口有没有报错,返回字段是否符合约定,关键流程走到了哪一步,都可能成为排查线索。

他也区分了本地写日志与完整诊断体系。具备写入能力后,还需要解决如何按问题获取记录、如何分析,以及怎样关联不同用户或场景的信息。对平台而言,要提供工具;对业务研发而言,则要知道在什么位置记录什么内容。

止损能力要在事故之前准备

他让听众想象一项已经接入的第三方 SDK 在启动阶段引发大量崩溃。团队没有它的源码,等新版本过审和用户升级又太慢,能立即做的事情取决于事故前是否预留开关:若服务端能关闭这项非核心能力,至少有机会让用户重新打开应用。代价是对应功能暂时不可用,所以必须结合影响范围判断。若故障与某次接口下发或实验调整同时出现,先撤回那项变更可能更直接。

第三个难题是线上突发问题。丰亚东以第三方 SDK 或服务端数据变化影响客户端为背景,提出对风险较高的接入和改动预先保留控制能力,例如可以关闭某项非必要功能的开关。

问题发生时,还应检查相关时间段的服务变更或实验调整,判断是否能够从这些环节回退。他也提到启动保护等思路,让连续启动失败的用户有机会恢复使用,而不只是等待下一次应用更新。

这些办法的共同前提,是系统已经具备相应控制与恢复路径。关闭一个功能也可能影响其原本提供的能力,因此目标是根据问题降低损失,并继续推进正式修复。

工具展示与后续方向

用不同工具回答不同的诊断问题

内存图演示里,丰亚东从一组占用很高的图片对象入手,沿引用关系看到业务回调仍持有它们,接着才提出缓存策略、图片解码等可能的检查方向。这个过程比只说“图片占了很多内存”多了一步:判断对象为何没被释放。另一个 core dump 演示则让调试器查看崩溃时保留下来的寄存器、栈和部分内存,帮助分析野指针等堆栈难解释的问题。它可以增加现场线索,不能把未保存的运行历史重新生成出来。

丰亚东展示了团队当时的几类工作。第一类是面向开发者的性能分析工作台,通过设备连接观察运行指标,并结合符号信息、火焰图及线程状态等,帮助寻找耗时或阻塞。他强调降低使用门槛,使开发者更容易进入分析过程。

第二类是线上内存图分析。示例从占用较大的对象出发,沿引用关系追踪到业务持有路径,进而考虑图片使用、缓存或并行处理等可能的问题。第三类则是保留崩溃现场的更多状态,通过 core dump 与调试器检查可用的寄存器、栈和相关内存信息,为仅靠堆栈难以解释的问题补充上下文。

三类工具分别关注运行过程、对象关系和错误现场。演示中的权限与能力取决于当时的环境,不能推广到任意应用;core dump 留下的是部分现场状态,也不能完整重放所有历史行为。

让监控回到具体业务场景

在最后的展望里,他对比了后端工程师和客户端工程师的日常:后端服务常有按服务拆分的指标,负责人会盯自己的报表;客户端作为一个安装包,整 App 的启动或崩溃数据却未必能告诉某个页面负责人自己该做什么。按场景、页面或模块聚合,再与相应服务端指标关联,才能让监控从平台的大盘走进业务开发者的工作台。这是他提出的方向,录播没有给出已经完成的一站式实现。

分享收尾时,丰亚东提出几个继续投入的方向:让问题更早暴露,扩大自动化测试覆盖,减少告警、归因、分发与响应之间的人工衔接,以及让监控信息更贴近开发者负责的场景。

他特别讨论了客户端开发者为什么可能较少主动查看整体应用指标。一个负责某个页面或模块的人,面对全应用的数据容易受到无关信息干扰。如果能够按页面、业务场景组织指标,并与相关服务的信息相互连接,数据就更容易进入其日常工作。

另一个愿望是共享疑难问题的处理经验。不同产品可能遇到相近的根因,已经解决的问题如果只留在某个人的记忆里,就难以帮助其他团队。

成长对谈上:能力与责任怎样一起变化

从完成任务,到负责一个方向与一支团队

主持人问成长阶段,丰亚东先从最普通的交付说起:新人收到任务后,代码质量和完成效率要能让团队放心;随后才谈到方向负责人主动识别问题、做中短期计划、带同事推进;团队管理者还得比较方案、判断当前业务的瓶颈,并把合适的人放到合适的位置。他特别指出“自己是优秀工程师”与“整个团队能打好仗”之间没有自动等号,责任范围改变后,衡量工作的方法也要改变。

在上半段访谈里,丰亚东用三个角色解释自己的成长观察。执行者首先需要把分配的任务按质量和效率要求完成,并在解决问题时积累经验;方向负责人则需要更主动地发现问题、设计方案、安排中短期工作,并推动项目落地。

到了团队管理者,要求进一步扩大:理解业务阶段,识别关键瓶颈,比较不同方案,制定较长周期的规划,也要认识成员各自的能力与协作特点,让人与工作形成合适的匹配。

这不是每个人都必须按同样年限走完的晋升路径。其核心区分是责任范围:个人技术很强,不自动等于团队整体就能高效工作;负责的范围扩大后,还要帮助其他人完成任务和成长。

在校园里,把兴趣、目标与信息连接起来

丰亚东举自己的通信工程背景为例:专业学习让他接触相关知识,但真正想做软件,是参加编程和软件竞赛后才逐渐确定的。接着他让学生从毕业后的目标往回倒推:若想读研、出国或直接进入某个开发方向,今天要准备的材料和能力并不一样。主持人进一步把顺序说清:接触足够多的信息,才能校正兴趣与目标;只在身边少量样本里比较,容易把一个偶然选择当成唯一的路。

面向学生,丰亚东先谈兴趣。他回忆自己学习通信工程时,通过接触编程与相关活动,逐渐发现对软件开发更有兴趣。专业名称并不自动决定个人之后最愿意投入的方向,需要通过实际接触形成判断。

接着是目标:毕业后想继续学习、出国还是工作,不同方向需要不同准备。明确一个阶段性目标,才能反过来安排今天的行动。第三点是拓展视野,通过资料、前辈交流与行业信息理解目标领域,避免只根据身边有限的信息作决定。

主持人将这三点联系起来:兴趣影响愿意投入的方向,信息帮助修正判断,目标再把它们转化为行动。这是两人的个人经验;如果还没有确定方向,也可以先通过尝试和交流收集信息。

职业选择,既看现实条件,也看自己想获得什么

他给这番话提供了自己的两次经历。第一次,所在公司调整业务,他被动转到不认可的新方向,虽然已有晋升机会,仍选择寻找更符合成长目标的环境。第二次是在字节继续做业务和转向基础架构之间做选择:基础能力会面对多个产品,对技术广度与深度的要求也更符合他的兴趣。他说不可能预知哪条路一定正确,主持人又补充薪酬是现实条件;两人最后强调的是不要只靠单一指标决定去留。

谈到对自己帮助最大的因素,丰亚东再次提到兴趣,并回顾进入 iOS 开发的契机:对编程、苹果产品与相关技术产生好奇,愿意继续投入时间了解。

他随后讲了两次选择。一是在业务调整后,重新考虑工作环境是否符合自己的成长目标;二是在继续做业务与转向基础架构之间,选择了更感兴趣的技术方向。他看重基础能力可以被多个业务使用,也希望进入更深的技术问题。

他并未声称当时能够预见结果,而是强调选择应与自己的兴趣和目标相协调。主持人补充,薪酬是现实考虑,但不应成为唯一标准;两人都没有主张忽略收入,或要求每个人按丰亚东的路径换工作。

与选择相关的另一点,是主动寻找新的挑战。当熟悉的工作已经能够顺利完成时,可以继续观察更有经验的人怎样处理问题,尝试承担新的难题,而不是只用当前团队中的相对表现衡量自己的能力。

专家能力要体现在解决问题与带动他人上

对于技术专家的成长路径,丰亚东首先强调专业能力。团队遇到架构、性能、稳定性或疑难问题时,是否有人愿意主动向你求助,能否把问题向前推进,是比头衔更具体的观察方式。

此外,专家还需要理解技术方向的长期变化,提出可落地的方案,并带领不同经验水平的同事一起完成复杂工作。即使不承担正式管理职责,也不能只关注自己的局部实现。能够解决难题,同时帮助团队建立方法和经验,才会扩大个人工作的影响。

学习资料与团队成员的观察重点

谈书单时,他并不是只列“必读书”。面对刚入行者,他提到 iOS 多线程与内存管理、Effective 系列,帮助形成对常见风险的敏感度;面对基础技术方向,则建议进一步理解链接、装载、内存与系统原理。一些书里的平台或版本已旧,但概念可以和当前工具链相互对照。他也说技术周报和交流不必篇篇读透:先记住别人解决过哪类问题,等自己遇到相似场景再去查原文、动手验证。

学习部分,丰亚东区分了编程习惯与底层知识两类材料。他提到 Effective 系列帮助留意设计选择与潜在风险,也推荐从链接、装载、内存及系统原理等方向深入。他以《程序员的自我修养——链接、装载与库》为例,讨论不同平台的知识怎样相互启发;同时也提醒某些材料需要结合年代阅读。

文章、周报、会议与技术交流则提供持续的信息入口。他不要求把遇到的每个知识点都立刻研究到底,而是先建立基本认识,知道问题相关的资料在哪里,需要时再深入。这是一种把零散线索转化为后续学习路径的方式。

谈到团队成员,他重视计算机基础、具体领域的理解,以及沟通协作与主动推动工作的能力。没有做过 APM,并不意味着不能体现技术深度:做界面与交互的人,也可以深入理解布局和绘制。能够解释自己完成过的代表性工作,比单纯列出接触过的技术名词更有意义。当时谈到的招聘需求可能已经变化。

成长对谈下:自研平台怎样建立价值与信任

需要 APM 能力,不等于都要自建完整平台

下半段访谈首先回顾团队成立的背景。丰亚东描述,业务与应用数量增长后,原有基础设施难以完全满足需求。第三方平台提供了已有能力,但团队还希望补足某些问题类型的观察,并与内部告警、归因和负责人分发流程连接。这些需求促成了自研方向。

当主持人问什么样的团队需要 APM 时,他将能力需求与实现方式分开。即使团队小,也应知道产品在线上是否出现严重问题;但可以先使用符合资源条件的现成、开源或采购方案,优先覆盖最重要的稳定性场景。业务与研发规模扩大之后,再决定投入哪些更细的能力。

因此,整场分享展示的全景图不应被理解为每个团队起步时都要建设的清单。需要观察和处理用户体验问题,与需要复制一个大型公司的组织和技术平台,并不是同一项决定。

团队、优先级与持续投入,要一起准备

他把“只有客户端同学写一个 SDK”与“团队能长期提供 APM 服务”分开。前者也许能采集事件,后者还得接收、聚合、分析数据,并让研发在页面上查到原因。若后端人力只是别组临时借来一两个月,第一版做完却没人值守,监控平台本身可能成为新的不稳定因素。因此,决定自研前要落实谁来维护整条链路,以及管理层是否认可长期投入。

如果决定自研,丰亚东首先关心人员与责任是否能够覆盖完整工作。客户端采集只是其中一部分,数据还要经过接收、处理和分析,并通过界面被开发者使用。规划、测试和后续维护也需要有人承担。

他担心临时借人完成一段开发后就无人负责,导致系统难以持续可用。因此,推动者需要争取相应的跨职能协作和长期投入,而不只是找到人把第一版写出来。

第二个问题是排序。他倾向先解决线上的主要稳定性问题,再逐步扩展,但也强调产品形态会改变重点:短视频等场景对流畅度和功耗的感受尤其直接。负责人需要根据真实用户问题安排工作,不能只按工具实现难度或个人兴趣列计划。

用可对照的证据说服业务接入

丰亚东用一个假设数字说明业务负责人的顾虑:同一批崩溃,原平台看见 100 次,新平台只看见 90 次,业务为什么要换?即使两边数量接近,也要把没采集到的事件逐个查清:是 SDK 本身异常,还是处理、上报链路中断?平台团队需要给自身采集过程也留下监控。只有通过同版本、同条件的反复比较,才有资格谈覆盖能力和新增的卡死、内存等价值。

第三个问题非常具体:业务已经用了其他平台,为什么要换成你的?丰亚东认为,自研身份本身不是充分理由。需要知道自己的监控是否完整、SDK 是否稳定、对性能有什么影响,以及能为业务提供哪些此前得不到的信息。

他讲到用同一版本、同类问题的数据进行对比。如果某些事件在现有平台中出现,而自研系统没有记录,就要检查采集、处理或上报流程在哪里中断。监控系统本身也需要能被观察,才能不断修正自己的缺陷。

通过一轮轮比较和改进,平台团队才能拿出更有说服力的接入依据。重点不是宣称指标一定胜过对方,而是能解释差异、提供证据,并提前与业务约定可以切换的条件。

接入受阻时,先缩小承诺并把一件事做稳

主持人追问当年最难熬的是什么,他没有把接入受阻归咎于业务“不配合”:新监控 SDK 若自己引入崩溃或耗电,一次事故就会伤害信任。他回忆早期几个月里反复收到负反馈,团队于是把目标收窄,先把崩溃监控做好,再和业务预先约定什么数据与稳定性条件达到后才切换。要求对方接入,必须先能回答接入会带来什么收益与风险。

丰亚东坦言,平台早期推进并不顺利。新 SDK 自身如果带来稳定性或性能问题,会影响业务信任;业务对接入价值提出质疑,也是一种正常反应。

团队的应对是收拢范围,先把最重要的一项能力做好,例如先专注崩溃监控,而不是同时承诺所有性能方向。较小的范围有助于控制风险,也更容易用实际结果检验质量。

与此同时,提前沟通验收条件,明确满足哪些要求之后再接入,减少双方只凭印象判断。范围、数据和沟通共同帮助平台逐渐建立信任,而不是依靠组织身份要求所有业务接受。

平台成熟之后,继续从未解决的问题找方向

主持人把第二种困惑概括为“功能都做得差不多,还能做什么”。丰亚东没有为了证明团队有事可做而列新名词,而是回到未解决的问题:线上出现崩溃时,能否快速确定根因?内存和卡顿是否仍有无法解释的长尾?已在线上发现的问题,能否在测试阶段提前暴露?这些问题若真实存在,才构成继续投入的理由;一个告警面板的功能齐全,并不等于开发者的排障过程已经顺畅。

另一种困惑出现在做了几年之后:常见功能已经具备,是否进入了只需维护的阶段?丰亚东的回答是回到业务痛点。疑难问题仍然可能无法归因,内存、卡顿等方向也还有深入空间;此外,还可以把能力扩展到上线前检测及统一修复等环节。

他说的进一步成长,不是为了持续有项目而增加功能,而是检查开发者是否仍有难以解决的问题,再判断团队能够在哪个环节发挥作用。这与技术分享开头提出的全流程视角相呼应。

个人成长与团队成果,怎样相互促进

最后一问讨论个人与团队的关系。丰亚东认为,个人能力提升可以改善方案质量与执行效率;业务进入更复杂的场景,又会给个人带来新的技术要求。两者可能形成相互促进的过程,但需要通过具体工作实现。

他分享了几条做事原则:愿意投入值得解决的问题;修复时尽量理解根因,避免只是换一种写法让问题暂时不出现;把已经验证的能力应用到更多业务,并通过文档和分享沉淀经验。对平台团队来说,一次可靠的改进可能服务多个产品,整理过的材料也能帮助更多开发者。

主持人最后补充了不同岗位的现实约束:业务团队有时需要接受成本与效果之间的折中,平台团队则有理由为共同能力投入更深。这个补充不能省略,否则容易把深入解决问题误读成所有工作都必须追求同一种投入尺度。

完整原片、分段信息可从第 5 期访谈页继续查看。

查看本期原视频与分段信息:我在字节做 APM →