小内存服务器最怕的,不是内存一直不够用,而是内存突然不够用。我手头那台 4GB 内存的 Debian 服务器,跑着 Nginx、MySQL 和一堆 Python 定时任务,前一阵几乎每隔两三天就要经历一次 OOM killer 的深夜绞杀:某个服务进程毫无征兆地消失,盯监控才发现是被内核干掉了。后来我花了一个周末,用 zram 做了一层压缩交换,再配合几个内核参数,把这种突发 OOM 的情况基本终结了。整个过程中 GitHub Copilot 和 Codex 帮我读内核日志、生成排查脚本、量化压测结果,省了不少力气。这篇文章就把这次的完整思路、踩坑记录和最终配置原样放出来,给同样在低配服务器上死磕内存的朋友当参考。
1. 突发 OOM 是怎么把我搞崩溃的
1.1 事故现场:进程被 OOM killer 精准“点名”
第一次崩是凌晨 3 点多。MySQL 客户端突然连不上,日志里dmesg直接给我拉了一长串 Out of Memory 记录。当时我第一反应是 MySQL 内存泄漏,结果查了半天发现根本不是它的问题。真正的情况是,某个 Python 定时任务一次性加载了大块数据,把系统可用内存瞬间打穿,内核触发 OOM killer 之后,按 oom_score 选中了 MySQL——因为 MySQL 是个常驻进程,RSS 在那一票进程里排得比较靠前。
典型的现场长这样:
[Wed Apr 16 03:21:44 2025] python3 invoked oom-killer: gfp_mask=0x100cca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0 [Wed Apr 16 03:21:44 2025] CPU: 1 PID: 3201 Comm: python3 Not tainted 6.1.0-18-amd64 ... [Wed Apr 16 03:21:44 2025] Out of memory: Killed process 986 (mysqld) total-vm:1672112kB, anon-rss:651200kB, file-rss:4kB, shmem-rss:0kB, UID:116 pgtables:420kB oom_score_adj:0注意这里有个很迷惑的地方:触发 OOM 的进程是 python3,最后被杀死的却是 mysqld。这在小内存服务器上并不少见。内核的 OOM killer 不是在杀“罪魁祸首”,而是在杀“最适合被回收的进程”,哪个进程牺牲掉能最快释放物理内存,它就杀谁。所以就算 MySQL 自己没吃多少额外内存,也可能被拉去垫背。
这种问题的麻烦点在于:它不是每天固定发生,而是隔几天一次,且每次都挑凌晨这种关键时刻。你没法靠控制单个服务来解决,因为源头是多变的:Python 任务、MySQL 临时表、Nginx 缓存、甚至系统日志解析都可能成为导火索。
1.2 为什么会突然出现“内存尖峰”
很多朋友一看到 OOM 就默认是“内存泄漏”,其实不全是。我那次排查下来,内存占用总量并没有稳步上涨,畸形的是瞬时分配曲线:某个瞬间 free 内存从 800MB 直接掉到不到 100MB,然后触发内核的紧急回收,回收速度跟不上分配速度,最终 OOM。
这里面涉及的机制其实不复杂。Linux 内核并不会等你把内存彻底用完再去回收,它会在内存水位降到一定程度时启动异步回收,正常情况下kswapd会把页缓存里的冷页换出去、把脏页写回磁盘。问题在于,小内存服务器留给你的“缓冲水位”太薄,一大块内存申请过来,水位线瞬间被击穿,异步回收还没搞定,同步的直接 reclaim 也随之失败,内核只能启动 OOM killer。
可以这么类比:内存就像一条高速公路,平时有点堵但车还能慢慢走;OOM 相当于某个瞬间大量车同时冲进一个窄匝道,收费站来不及疏导,后面的车直接顶在一起,再好的调度也救不回来。小内存服务器因为车道本来就少,对瞬时流量的容忍度极低。
这也解释了为什么稳定的内存占用反而不太容易触发 OOM——真正难防的就是这种“尖峰”。尖峰最常出现在定时任务、批量数据处理、编译、备份解压、加载大模型这类场景里。
1.3 为什么不直接加内存
说实话,加内存是唯一的根治方案。但现实情况是:那台机器我已经买了好几年,云厂商给的扩容价格比机器本身还贵;而且这是个边缘节点,不值得再投入。很多朋友的处境应该和我一样——不是不知道加内存好,而是当前条件下没法加。
既然物理内存没法变,能做的就是调整“虚拟内存层”的策略。让内核在压力尖峰到来时,有更多缓冲手段,而不是直接走到杀进程那一步。这时候,zram 就进入了我的视线。
2. 调优思路:为什么最后选了 zram + AI 辅助
2.1 排除掉几个看起来合理但不够用的方案
最先想到的是磁盘 swap。很多教程都会告诉你:内存不够就建 swapfile,4GB 内存就分 4GB 交换分区。我试了一个晚上就放弃了。原因是这台服务器的 SSD 性能一般,一旦把进程换出到磁盘,突发尖峰时会有大量磁盘读写,整台机器卡到 SSH 都敲不动字;而且瞬间内存压力下,内核要频繁 swap-in/swap-out,磁盘 IO 反而成为新的瓶颈。这就相当于用一条更慢的高速公路去缓解原本就拥堵的路口,效果糟糕。
也考虑过 zswap。zswap 的原理是拦截交换出去的页面,先压缩暂存在内存,等内存进一步紧张再写回后端磁盘 swap。它的优势是减少磁盘 IO,但前提是系统里必须有一个后端 swap 设备。我不打算用磁盘 swap,所以这条路天然不合适。
应用层优化也想过:限制每个 Python 任务的内存、给 MySQL 调innodb_buffer_pool_size,这些确实能做,但治标不治本。第三方库的内存行为并不完全受我控制,而且总不可能把所有服务都限制到永远不会超内存的程度。
最后剩下 zram。它直接在内存里建一个压缩交换设备,交换出去的页面会被压缩后继续留在物理内存中,而不是写到磁盘。这样既没有磁盘 IO 延迟,又能让内核多一层“弹性缓冲”,非常契合小内存服务器的尖峰场景。
2.2 zram 是在用有限的物理内存换“缓冲余地”
zram 的原理可以这样理解:它把本来要被交换出去的页面压缩,然后继续放在内存里,只不过占用的空间小了很多。如果压缩率是 2:1,那么原本需要 1GB 物理内存的页面,压缩后只需要 500MB。内核看到的内存大小是假的,但能存放的数据量是真的变多了。
这也是为什么很多人把 zram 叫做“压缩交换”。它并不是真的凭空造出内存,而是把内存里冷数据占用的空间压缩,腾出物理内存给热数据。配合内核的交换逻辑,当突发内存尖峰出现时,kernel 可以把不常用页面快速压进 zram,释放物理页给正在申请内存的进程,避免直接 OOM。
举个我实际观察到的例子:4GB 内存的机器,平时可用内存约 400MB 到 800MB,某次 Python 任务突然申请 2GB 内存时,如果没有 zram,内核大概率直接触发 OOM;而有 zram 之后,内核会先把 MySQL、Nginx 等服务的冷内存页面压缩并放到 zram 里,腾出物理内存让 Python 任务完成分配,整个过程没有进程被杀。这里面真正发生的事是“把仓库里不常用的大箱子压成了小箱子,腾出货架给新到的货”,而不是“凭空扩了仓库”。
当然,zram 也不是万能药。如果申请的内存总量超过了“物理内存减去正常进程占用后,再加上 zram 能释放的压缩空间”,内核照样会走 OOM killer。它只是在阈值到来之前给你争取了大量缓冲时间。
2.3 Copilot 和 Codex 在这里能帮上什么忙
这次调优和以前不太一样的地方是,我全程用 GitHub Copilot 和 Codex 做了一部分“思维外挂”。一开始我也不太信 AI 能处理内核配置这种偏底层的活儿,但实际用下来发现它们能帮到三个地方:
第一,日志解析。OOM 日志信息密度高,字段又多,人工一条条读很费眼神。Copilot 可以直接把dmesg里那段 OOM 上下文翻译成人话,告诉我哪些字段值得关注,比如anon-rss代表进程匿名页、pgtables是页表开销、oom_score_adj会影响被选中的优先级。这比我手动翻内核文档快得多。
第二,脚本生成。我需要一份能够持续记录 zram 压缩率和内存压力的监控脚本,Copilot 很快就给了我一个 Python 轮询版本,稍作修改就能跑。没有它,我可能还得先查半天/sys/block/zram0/mm_stat字段格式。
第三,方案校验。Codex 会在对话里针对我贴出的free、zramctl输出给建议,有些建议很中肯,有些则踩了坑。这个我在第 4 节专门展开说。
但也要强调:AI 只是辅助,最终判断得自己来。它不知道我这台服务器是 2 核还是 1 核、跑的是数据库还是纯静态站,更不知道我上个月已经因为改错参数崩过两次。所以我的原则是:让 AI 帮我出脚本、读日志、找思路,但所有落到系统上的配置,必须经过压测和观察验证才敢生效。
3. 实操:从加载模块到 sysctl 参数的完整配置
3.1 确认内核支持并加载 zram 模块
zram 在主流 Linux 发行版里基本都编译成了模块,但部分精简版云主机内核可能没带。动手前先确认一下:
# 查看内核配置文件里有没有 zram grep -E 'CONFIG_ZRAM|CONFIG_CRYPTO_LZ4|CONFIG_CRYPTO_LZO|CONFIG_CRYPTO_ZSTD' /boot/config-$(uname -r) 2>/dev/null || true grep CONFIG_ZRAM /proc/config.gz 2>/dev/null || true如果看到CONFIG_ZRAM=y或者CONFIG_ZRAM=m,就说明支持。接下来,加载模块并创建设备:
sudo modprobe zram num_devices=1 lsmod | grep zram ls -l /dev/zram*常见情况是模块在,但/dev/zram0没有被自动创建,这时/sys/class/zram-control可以帮忙,不过更省事的做法是直接 modprobe。如果你用的是 Debian/Ubuntu,有可能需要先装linux-modules-extra-$(uname -r)这类包,否则 modprobe 会提示没有这个模块。
3.2 大小怎么算:压缩率、可用内存与工作负载
zram 的disksize参数是最容易让人误解的地方。它不是“立即占用多少物理内存”,而是“逻辑交换设备的最大容量”。真正占用的物理内存在/sys/block/zram0/mm_stat里的mem_used_total字段能看到,它表示所有压缩页占用的实际内存总大小。
mm_stat一行八个数字,单位都是字节:
orig_data_size compr_data_size mem_used_total mem_limit mem_used_max same_pages pages_compacted huge_pages我一般用一条命令直接换算成可读格式:
awk '{printf "orig=%dMB compr=%dMB mem_used=%dMB ratio=%.2f\n", $1/1048576, $2/1048576, $3/1048576, $1/$2}' /sys/block/zram0/mm_stat压缩率越高,zram 的收益越明显。正常文本、代码、日志、匿名内存页的压缩率通常在 2:1 到 3:1 之间;如果遇到已经压缩过的图片、视频或者加密数据,压缩率可能只有 1.1:1,甚至接近 1:1,这时候 zram 的效果就很差。
大小怎么定?我给的一个经验值是:zram 的逻辑大小设为物理内存的 50% 到 75%。以我这台 4GB 机器为例,我最初设了 3GB,后来调成 2GB。原因是:zram 占用的物理内存会随着使用量增加而增加,如果设得太大,在极端场景下反而会挤压正常进程。3GB 逻辑空间如果压进去 1.5GB 实际内存,那系统可用的物理内存就少了 1.5GB,风险不小。
我自己监控下来,4GB 内存时 zram 逻辑大小 2GB 比较舒服:平时压缩数据占用物理内存在 300MB 到 800MB 浮动,遇到突发尖峰时能冲到 1.5GB 左右,但很少影响正常业务。如果 zram 逻辑大小只有 1GB,遇到大任务又不够用;设到 3GB 则让 CPU 和内存都变得紧绷。
3.3 压缩算法选择:zstd、lz4 还是 lzo
zram 支持的压缩算法取决于内核编译时开了哪些选项,常见的有 lz4、lzo、zstd。查看当前可用算法:
cat /sys/block/zram0/comp_algorithm选型逻辑很简单:CPU 弱、负载高选 lz4;内存极其紧张、CPU 有富余选 zstd;lzo 属于折中方案,老内核默认带得多。
| 算法 | 压缩率(大致) | 压缩速度 | 解压速度 | 适用场景 |
|---|---|---|---|---|
| lz4 | 中等 | 极快 | 极快 | 低配 CPU、高并发读写 |
| lzo | 中等 | 快 | 快 | 老内核、兼容性优先 |
| zstd | 高 | 较快 | 快 | 内存紧张、CPU 有余量 |
我这台机器是 2 核,跑的是 Nginx、MySQL 和 Python 任务,CPU 平时不算忙,所以最后选了 zstd。压测下来,zstd 在 4GB 机器上的压缩率能到 2.5:1 左右,CPU 占用在突发时上升 10% 到 15%,可以接受。如果你的服务器平时 CPU 已经跑满,建议换 lz4,别为了那点压缩率把正常业务拖垮。
配置压缩算法是通过 sysfs 完成的:
echo zstd > /sys/block/zram0/comp_algorithm注意,要先确保算法在comp_algorithm输出列表里,否则这条命令会直接报错。
3.4 systemd 配置开机自启
直接命令行配置 zram 很容易,但服务器一重启就全丢了。我更推荐用 systemd 把整个过程固化下来。如果你用的是 Fedora、RHEL 或 Arch 这类带zram-generator的发行版,写配置文件最简单:
# /etc/systemd/zram-generator.conf [zram0] zram-size = min(ram / 2, 4096) compression-algorithm = zstd swap-priority = 100如果你的发行版没有 zram-generator,或者你想完全掌控流程,可以用通用 systemd unit 加一个配置脚本。我的方案是这样的:
# /usr/local/sbin/zram-setup.sh #!/usr/bin/env bash set -euo pipefail DEV=/dev/zram0 SIZE=2G ALGO=zstd start() { modprobe zram num_devices=1 if swapon --show=NAME --noheadings 2>/dev/null | grep -q "^${DEV}$"; then echo "${DEV} already in use" >&2 exit 0 fi echo "${ALGO}" > /sys/block/zram0/comp_algorithm echo "${SIZE}" > /sys/block/zram0/disksize mkswap -f "${DEV}" swapon -p 100 "${DEV}" } stop() { swapoff "${DEV}" 2>/dev/null || true echo 1 > /sys/block/zram0/reset } case "${1:-}" in start) start ;; stop) stop ;; *) echo "usage: $0 {start|stop}" >&2; exit 2 ;; esac然后创建 systemd service:
# /etc/systemd/system/zram-swap.service [Unit] Description=ZRAM swap setup After=systemd-modules-load.service [Service] Type=oneshot RemainAfterExit=yes ExecStart=/usr/local/sbin/zram-setup.sh start ExecStop=/usr/local/sbin/zram-setup.sh stop [Install] WantedBy=multi-user.target记得:
chmod +x /usr/local/sbin/zram-setup.sh sudo systemctl daemon-reload sudo systemctl enable --now zram-swap.service sudo systemctl status zram-swap.service最后再确认一下:
zramctl swapon --show看到/dev/zram0出现在 swap 列表里,优先级 100,就说明配置成功。用swapon -p 100给 zram 一个高优先级,就能保证内核优先使用 zram 而不是其他磁盘 swap,这个顺序非常重要。
3.5 同步调整的四个内核参数
只建 zram 还不够,内核的默认回收策略是按磁盘 swap 设计的,直接搬来用在 zram 上会浪费它的优势。我最终在/etc/sysctl.d/99-zram.conf里写了这几个参数:
vm.swappiness=150 vm.page-cluster=0 vm.vfs_cache_pressure=80vm.swappiness=150是这次调优里最关键的一步。默认值 60 表示内核在换出页面时会比较克制,优先回收页缓存;但 zram 的换入换出成本远低于磁盘,所以我们应该让内核更大胆地把冷页压进 zram。Linux 5.8 之后 swappiness 可以设到 200,我给的是 150,兼顾了换出积极性和 CPU 消耗。设太高会导致页面频繁压缩解压,白白吃 CPU;设太低又等于没发挥 zram 的缓冲作用。
vm.page-cluster=0用来关闭 swap 预读。对磁盘 swap 来说,一次读取多页能减少寻道次数;但对 zram 来说,随机单页读取本身就很快,预读反而会导致每次需要换入时多解压好几个用不到的页面,纯属浪费。这个参数设成 0 是 ChromiumOS 等使用 zram 的系统的常见做法。
vm.vfs_cache_pressure=80是可选项。它让内核不那么急着回收 inode 和 dentry 缓存,适合跑文件类服务的机器。如果你主要是内存计算,不涉及大量文件操作,这个参数可以不动。
另外提醒一句:vm.overcommit_memory别乱改,保持默认 0 就好。网上很多文章为了防 OOM 会建议设成 2,但那样内核会严格限制虚拟内存承诺,导致进程即使有 zram 缓冲,申请内存时也可能直接报cannot allocate memory,属于把问题提前到错误的位置爆开。
配置完记得重载并验证:
sudo sysctl --system sudo sysctl vm.swappiness vm.page-cluster vm.vfs_cache_pressure4. 用 Copilot/Codex 排查与验证的完整记录
4.1 让 AI 读 OOM 现场日志
配置上 zram 之后,我并没有急着验收,而是先翻出了之前那几次 OOM 的现场日志。我习惯先把日志导出成文件,再把文件内容整个丢给 Codex 分析。给 AI 的上下文越完整,它给出的判断越有价值。
我当时给 Codex 的提示大概是这样:
这是一段 dmesg OOM 日志,请帮我分析: 1. 触发 OOM 的进程是什么?它申请内存为什么会失败? 2. 被杀进程的 anon-rss、swap、shmem 分别是什么,说明什么问题? 3. 日志里有没有提到 zone 水位(DMA32/Normal)告警?如果需要看,还要补充哪些数据? 4. 基于这些信息,下一步我应该重点检查哪三件事?Codex 很快指出:日志里order=0说明不是碎片问题,而是单纯的页分配失败;anon-rss:651200kB说明 mysqld 的匿名页占了不少物理内存;还提醒我可以去看/proc/pressure/memory里的 full 指标,判断是否有进程因为内存短缺而长期阻塞。这比我一开始只会盯着free -h发呆高效多了。
不过这里有个关键前提:必须贴上完整的关键日志,不要只贴最后一两行。OOM 日志里大概率有触发的具体进程、各进程的内存排行、zone 信息,这些字段全是线索。我一般用这条命令把现场抓下来:
sudo dmesg -T | grep -A 50 -Ei 'oom|killed process' | tail -60如果机器已经重启过,dmesg里的记录会丢,那就需要看 journald:
sudo journalctl -k -b -1 | grep -B 5 -A 50 -Ei 'out of memory|oom-killer'4.2 用 AI 生成监控脚本,把压缩收益量化出来
调参数不能靠玄学,得用数据说话。我让 Copilot 帮我写了一个 Python 小脚本,每隔一分钟记录 zram 压缩率、内存压力指标和 swap 使用情况,输出到日志文件。核心部分长这样:
#!/usr/bin/env python3 import datetime import pathlib import time ZRAM_STAT = "/sys/block/zram0/mm_stat" LOG = "/var/log/zram_monitor.log" def read_zram(): with open(ZRAM_STAT) as f: vals = list(map(int, f.read().split())) return { "orig": vals[0], "compr": vals[1], "mem_used": vals[2], } while True: try: z = read_zram() psi = pathlib.Path("/proc/pressure/memory").read_text().splitlines()[1] ratio = z["orig"] / z["compr"] if z["compr"] else 0.0 line = ( f"{datetime.datetime.now().isoformat()} " f"orig={z['orig'] / 1048576:.1f}MB " f"compr={z['compr'] / 1048576:.1f}MB " f"ratio={ratio:.2f} " f"mem_used={z['mem_used'] / 1048576:.1f}MB " f"psi={psi}" ) with open(LOG, "a") as f: f.write(line + "\n") except Exception: pass time.sleep(60)部署之后我用 systemd timer 或 cron 每分钟跑一次,日志积累了两天。从数据里能清楚看到,正常运行时段 zram 的压缩率稳定在 2.3 到 2.8 之间,mem_used 一般不超过 600MB。而某次 Python 定时任务启动时,orig 从 1.2GB 快速涨到 3.5GB,compr 涨到 1.4GB,mem_used 冲到 1.2GB——也就是说,zram 用 1.2GB 物理内存吸收了约 3.5GB 的原始页面。如果没有 zram,这 3.5GB 的分配请求在 4GB 的机器上大概率直接触发 OOM。
4.3 压测复现:用一个可控 OOM 验证配置
靠日常日志验证还不够,因为运气不好可能一周也等不到一次真正的尖峰。我选择在业务低峰期用stress-ng主动制造内存压力,模拟突发尖峰。
# 安装 stress-ng,Debian/Ubuntu 示例 sudo apt install stress-ng # 模拟 2 个进程,各申请 1.2GB 内存,持续 120 秒 stress-ng --vm 2 --vm-bytes 1.2G --timeout 120s --metrics-brief在没有 zram 的原始状态下,这个压测大概率会让系统 OOM,而且被杀的很可能不是 stress-ng 自己,而是某个命运悲惨的常驻服务。配置 zram 之后,我连续跑了两轮,观察到的现象是:free 内存一度掉到几乎为 0,但 dmesg 里没有新增 OOM 记录;MySQL 和 Nginx 的进程都活得好好的;zramctl显示 swap 使用量短时间内涨了 1.5GB 左右。
我还特意测了一次极限压力:申请总量超过 3GB。这种情况下 OOM 依然会出现,但被杀的是 stress-ng 进程本身,而不是 MySQL 或 SSH。这个结果非常关键:zram 改变了“谁先死”的次序,把损失控制在了可承受范围内。
提醒一句:压测千万别在生产机器的高峰期做,最好挑凌晨或者直接起一台测试机。我第一轮压测时没注意,跑了半小时发现 Nginx 的访问日志出现大量 5xx,虽然最终没崩,但也吓得够呛。
4.4 AI 建议也要过一遍大脑:两个翻车例子
用 AI 调内核,最大的坑不是 AI 不会,而是 AI 给出的建议“看着太对了”。我这次就碰到两个典型的翻车。
第一个是 Codex 建议“zram 大小直接设为内存总量的两倍”。这个建议放在传统磁盘 swap 场景下没毛病:swap 越大,能扛的内存压力越多。但 zram 不一样,它的存储介质还是物理内存,设置成 8GB 意味着最坏情况下可能有几 GB 内存被压缩页占掉,正常业务反而先被饿死。AI 没有意识到“zram 是内存的虚拟扩展,而不是磁盘的替代品”,这种有条件的经验它会表现得特别自信。
第二个是 Copilot 第一次看到vm.swappiness就让我调低到 10,理由是“减少 swap 使用”。这在普通 SSD 场景下是主流降延迟思路,但放在 zram 场景就南辕北辙了:如果 swappiness 太低,内核根本不会把冷页压进 zram,越怕用 swap,越没法发挥 zram 的缓冲价值。后来我改成 150,压测数据说明这样更合理。
所以我的经验是:把 AI 当“擅长代码和日志分析的同事”,它给你提供候选方案和脚本,但最终是否上生产、参数怎么调,必须结合你自己的监控数据和压测结果。尤其是内核参数,很多建议在不同硬件、不同负载下结论正好相反。
5. 常见问题速查与避坑心得
5.1 常见问题速查表
实际操作中我踩过不少坑,这里整理成表格,方便后来人直接对照。
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 开机后 zram 没创建 | 模块未加载或 zram-generator 未安装 | modprobe zram;Debian/Ubuntu 装linux-modules-extra-$(uname -r) |
| zram 压缩率很低,不到 1.3:1 | 换入了大量已压缩/加密数据 | 看mm_stat确认;适当降低 swappiness,减少不可压缩页进入 zram |
| CPU 负载明显升高 | 压缩算法吃 CPU 或 swappiness 过高 | 换 lz4/lzo;调低 swappiness,观察 10 分钟 |
| 压测时系统卡死 | page-cluster 过高导致预读浪费 | 设置vm.page-cluster=0 |
| 配置后仍然 OOM 杀进程 | 内存需求超过物理内存 + zram 压缩空间总和 | 区分是整体内存不足还是 cgroup 限制;考虑优化应用层内存申请 |
| AI 给的参数重启后消失 | 只临时改了运行时参数 | 写入/etc/sysctl.d/和 systemd unit,重启验证 |
| swapon 时提示设备忙 | 之前已经启用过 zram | 先swapoff /dev/zram0,再echo 1 > /sys/block/zram0/reset |
5.2 几条说不上参数、但能救命的经验
第一,先监控再改参数。我是在部署监控脚本之后才发现,自己原本想设的 zram 逻辑大小偏小,而 swappiness 太低又导致 zram 长期吃不满。没有数据支撑的调优都是盲人摸象。
第二,改内核参数必须留后路。我一开始为了省事,直接命令行改完就完事,结果某次重启后参数全丢,凌晨又崩了一次。现在我的习惯是:所有改动都落在配置文件和 systemd unit 里,改完先手动压测,确认没问题再重启验证。
第三,重要进程要主动降低被误杀概率。zram 能把 OOM 概率压下去,但不能保证永远不触发。如果 MySQL 这类服务绝对不能被杀,可以单独设置 OOMScoreAdjust:
sudo systemctl set-property mysql.service OOMScoreAdjust=-500这样即使系统真的走到 OOM killer,MySQL 也会排在列表最后面,尽可能降低误杀概率。
第四,日志要尽量持久化。很多云服务器重启后,dmesg里的 OOM 现场会被清掉,等你第二天早上起来排查时,什么痕迹都没了。建议至少配置 journald 持久化,或者把dmesg定期导出到本地日志文件,否则下次 OOM 发生时你又会回到原点。
最后说一点我自己踩坑换来的心得。很多人觉得“用 AI 调内核”是个噱头,但实际操作下来,Copilot 和 Codex 最让我满意的不是它直接给出答案,而是它能逼着我把问题描述得更完整:要贴哪些日志、要查哪些指标、压测结果怎么解读。当你把上下文给它补全的时候,其实你自己对问题的理解也已经上了一个台阶。zram 这次调优能顺利落地,靠的是 AI 提效、监控数据和压测验证三方配合,而不是盲目相信任何单一来源的建议。