排查 OOM 应先确认分配范围
当一次分配在相关限制范围内无法满足,适当的回收尝试也不能解决问题时,Linux 可能调用内存不足终止机制。它是最后阶段的恢复手段,不是理解业务重要性的调度器。有些分配会直接失败而不杀进程,配置也可能使系统进入恐慌处理。因此,排查应先识别分配范围与实际事件,不能一开始就认定整台机器的每一字节内存都已经用完。 先看范围,再看评分 全局内存短缺与内存控制组短缺是两回事。某个控制组可以在主机仍有可用内存时达到自身上限;它的 OOM 处理不能任意挑选组外进程来解决组内问题。允许使用的内存节点等其他限制也可能影响分配。因此,主机剩余内存图表不足以证明或否定某次失败,更不能在没有检查事件范围时,把所有容器异常都归类为全局容量不足。 在适用的选择规则内,内存占用与调整值会影响牺牲者选择。内核并不知道哪个进程商业价值高、哪个空闲服务可以丢弃,也不知道哪一个业务必须持续可用。一个体积很大的数据库可能被选中,即使压力也来自许多小工作者。procfs 中的 oom_score 是可观察数值,不是承诺把所有可见进程排序以后,就能准确预测任何内存域下一次会杀谁。 理解调整值,不许诺必然存活 oom_score_adj 的范围为负一千到正一千。正值增加被选中的倾向,负值降低它;负一千会把该任务排除在这项内核 OOM 选择之外。一般负值并不保证存活,这个设置也不能阻止显式信号、服务管理器或独立用户态内存压力策略终止任务。调整分值不会增加容量,也不会修复泄漏。把它解释成“重要进程已经免疫所有内存故障”,会造成危险的运维误判。 保护所有任务不是可行的容量方案。如果没有合适的牺牲者,不能假定内核会越过控制组边界,去杀一个无关的监管进程。整组终止与恐慌设置需要分别分析。调整值可以被子进程继承,而持久服务设置应归入监管器配置;修改之前需要明确授权并验证适用范围。下面实验不会改动这些值,也不制造内存压力,避免把接口学习变成真实故障。 坏掉的样子 反复重启可能掩盖持续泄漏。如果同一个工作负载不断增长直至失败,增加内存也许只是延长两次失败之间的间隔。应关联分配速度、常驻和记账内存、负载大小、回收及交换活动。单个进程的常驻大小不能完整表示控制组全部费用或共享内存关系。降低上限可以约束增长范围,却也可能让应用更早失败;不能只观察邻居未被终止,就认为业务恢复问题已经解决。 退出状态一百三十七与 SIGKILL 相符,但并不是 OOM 专属证据。应查看运行时的终止原因、控制组内存事件计数和操作员可见的内核记录。分配失败、进程被杀、内核 OOM 和用户态压力管理器终止,是需要分开的情况。看不到内核日志,也可能只是当前观察范围受限,不能直接证明没有事件发生。诊断必须说明证据来自哪里、有哪些缺口。 评分会随内存和范围变化。读取 procfs 的过程中进程可能退出,容器内的进程视图也可能隐藏主机的大部分任务。一次成功读取并不是原子的整机快照。分析真实失败时,应保留事件时间与受影响的控制组,然后比较相同范围内的观察,而不是拿事后另一个进程的分数去解释之前的牺牲者选择。 快速验证 保存为 oom_observe.py,使用 Python 3.9 或更新版本执行 python3 oom_observe.py。程序只读取自身进程的两个评分文件,不创建压力负载,不修改任何限制或策略。两条确定性断言仅对明确虚构的记录测试展示顺序,实时读取和模型检查会分别报告结果,避免把一种成功误当成另一种验证。 """Read-only observations and a sorting test, not an OOM predictor.""" from pathlib import Path import os def snapshot(pid): base = Path('/proc') / str(pid) try: return (int(pid), int((base/'oom_score').read_text()), int((base/'oom_score_adj').read_text())) except (OSError, ValueError): return None def ranked(rows): return sorted(rows, key=lambda row: (-row[1], row[0])) fixtures = [(42, 700, 0), (17, 100, -500), (99, 700, 200)] assert [row[0] for row in ranked(fixtures)] == [42, 99, 17] assert ranked([]) == [] row = snapshot(os.getpid()) if row is None: print('SKIP: this process has no readable Linux OOM score files') else: pid, score, adjustment = row assert -1000 <= adjustment <= 1000 print(f'pid={pid} oom_score={score} oom_score_adj={adjustment}') print('Observed one process; no victim selection or memory pressure tested') print('PASS: deterministic sorting checks; live observation is reported separately') 虚构记录按分数降序排列,同分时按数字进程号排序,低分记录排在最后。代码没有根据虚构调整值重新计算内核评分。Linux 分支单独打印当前进程的分数和调整值;文件不可读时报告 SKIP,但仍执行排序断言。因此,末尾 PASS 不意味着实际的 OOM 处理已经发生,更不是一次牺牲者选择实验。 ...