我是个后端软件 Troubleshooting(故障解决) 爱好者。更准确地说,对 troubleshooting 已经有点像上瘾般的,难以自制的嗜好。这有时候是件好事,但有时候带出很多工作和生活上的困扰。无论如何,性格决定命运,既然改变不了,不如好好利用。
从业 20 年 troubleshooting 过各种类型的生产故障。本文只想讨论两种类型的故障:
- 资源耗尽(exhaustion)的故障
- 间歇(intermittence) 出现的故障
题外话。这是一篇水文,最近生活上出现了很多 “故障”,忙于 troubleshooting 生活故障了(BTW,我真的非常讨厌做生活的 troubleshooting),所以写点水文。也算丰富一下自己的写作范围吧,毕竟越水的东西,生命周期越长,不像具体技术。
资源耗尽(exhaustion)
Design for Failure 是所有软件架构设计者都知道的 robustness 原则。而 资源耗尽(exhaustion)
是 Failure 的一种类型。资源耗尽(exhaustion)
本质上有两层识别过程:
- 对资源依赖的全方位识别
- 对依赖资源的可用情况、需求情况有深入的分析
就两行文字,听起来很容易。但实践起来,是个全方位考验技术积累的活。这话怎讲?现代软件的设计,最大好处是有很多面向开发者的抽象的概念、编程方法、API。但在开发者享受抽象带来的简洁的同时,也同时必须承受抽象 “欺骗” 性带来的失真,以及失真后对细节把握不足的风险。哈哈,这句话说得太抽象了,不像我的风格了。举个例子:
用 Java 写代码时,没有多少人会关心创建一条线程:
- 需要什么操作系统资源
- 每种资源需要多少
人们总是假设,要么这是无限的,要么是使用量不大,不需要考虑。而很多时候,这也的确是符合工程需求的假设。但对于高并发/负载的应用,情况就不同了。以基于 Java/JVM 的 Kafka 对 Linux mapping in the virtual address space(mmap
) 的使用为例:
- 每条线程最少需要两个 mmap : stack 与 stack safe guard
- each partition requires minimum 2 map areas, as long as it hosts a single log segment. That is to say, creating 50000 partitions on a broker will result allocation of 100000 map - Kafka Docs
Linux 对每个进程的 mmap 数量有限制,vm.max_map_count 默认 65535 。在默认配置与大负载情况下,会出现这个情况:
[14.236s][warning][os,thread] Attempt to deallocate stack guard pages failed (0x00007f61a50a0000-0x00007f61a50a4000).
Java HotSpot(TM) 64-Bit Server VM warning: INFO: os::commit_memory(0x00007f61a50a0000, 16384, 0) failed; error='Not enough space' (errno=12)
# There is insufficient memory for the Java Runtime Environment to continue.
# Native memory allocation (mmap) failed to map 12288 bytes for committing reserved memory.
# An error report file with more information is saved as:
# /opt/kafka/bin/hs_err_pid2423.log
热点资源识别
上面说了,资源耗尽(exhaustion)
本质上有两层识别过程:
- 对资源依赖的全方位识别
- 对依赖资源的可用情况、需求情况有深入的分析
对于上面例子,需要在架构师、开发、测试、运维这几个角色当中,有最少一个人去深入了解过这个 “middleware” 的实现细节:
- 分析出资源依赖
- 识别出需要重点关注的资源
并识别出需要重点关注的资源 这个是最难的。因为一个 middleware/library 不是我们自己实现的,其中依赖的资源类型很多,如果每个都去分析,工程上是不可行的。所以需要有这个能力:
- 预估系统流量/负载
- 系统流量/负载如何映射/转换到目标组件
- 目标组件的流量/负载如何映射/转换到使用的资源类别中
说白了,就是 “热点资源识别” 能力。这个能力大概是这样建立起来的:
- 对组件的理解
- 对组件使用到资源的理解。
对组件的理解
可以用几个方法达到:
- 组件的文档。主要是组件运维管理相关的,如: admin docs/Monitor docs/Troubleshooting FAQ
- 源码。不可能全看,更多时候,是在故障发生后,客观分析故障原因和线索链时阅读
- 基础资源知识的积累。这是个长期的过程,包括 OS/Hardware
间歇(Intermittent) 出现的故障
Troubleshooting(故障解决) 几大任务:
- 现场分析
- 故障可能原因(root cause)列举
- root cause 证据链分析与收集
- 故障重现实验
- 提出 解决(fix) / 规避(Workaround) / 缓解(mitigation) 故障 的方法
- 故障解决实验
- 生产解决灰度发布
对于 间歇(Intermittent) 出现的故障
上面的所有任务都变得困难。我曾经花了数月时间,才重现了一个生产上的 间歇(Intermittent) 出现的故障
:
Istio/Envoy TCP Proxy half-closed connection leak for 1 hour in some scenarios
对于这类型的故障,如果事发时没记录相关的 log/metrics ,就只能凭经验去有方向性地猜和证明了。
后话
AI/GPT/LLM 大热后,人们开始有这样的想法:
- 大脑中的知识的积累并不重要,因为都可以需要时从 AI 问答中提取
- 程序员的计算机基础知识不重要,因为 AI 可以为我们代劳思考
关于第 1 点,我是有保留的,原因是 AI 可以给我们问题的答案,但对具体单位、项目、个人的特殊性个性的感知,还因训练数据源未打通而不及人类。只要人自己掌握了知识,结合对现实的感知,把知识有机串联起来,人掌握的知识才能比 AI 按需提取有价值。
计算机世界的闭环:在 AI 未能写代码之前,计算机世界是不能形成闭环的,但现在可以了。如果你把计算机作为一个主体,可以这么反过来说:
在 AI 编程之前,计算机需要人工辅助编程去定义工作。计算机工作的意义,由程序员定义。而在 AI 编程之后,计算机自己就完成了编程的闭环,工作的意义,可以由计算机自己定义。这,可能是个哲学问题了。
而人类研发的角色将离开这个内环
,放到更外的环,更贴近人类抽象思维的环上。直到有一天,内环
出了故障,而人类知之甚少。好像这也没什么大不了。
想想,CPU 也有 microcode 而又有多少软件开发者有需要了解 microcode,和在 microcode 出现 bug 时,能 troubleshooting 出来呢?
吾生也有涯,而知也无涯 。以有涯随无涯,殆已!已而为知者,殆而已矣!为善无近名,为恶无近刑。缘督以为经,可以保身,可以全生,可以养亲,可以尽年。
—— 《庄子·养生主》
面对现代知识的无限,谁都不可能真 “全栈” 。 但这个栈要学到什么深度? 每个人都有不同答案,交给读者去思考了。