zram 状态指标详解:mm_stat 的 9 列、io_stat 的 3 列与一套完整排障路径
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
线上某台 32G 内存的服务器,可用内存掉到 4% 以下,swap 开始工作后应用 P99 延迟从 80ms 爬到 600ms。这台机器的 swap 挂在 zram 上,理论上不走磁盘,为什么还会这么卡?登录机器后cat /sys/block/zram0/mm_stat一看:压缩后的数据量只比原始数据量小 8%,mem_used_max一路逼近 disksize,io_stat里的failed_writes还在持续增长。接下来这段内容,就照着内核里 zram 真正记录、导出这些数字的代码,把每个字段拆开讲,再给出对应异常下的排查顺序。
一个 4KB 页进入 zram 之后发生了什么
zram 本质是一个 RAM 块设备:页被"写"进来的时候不占原来的 4KB 槽位,而是压缩后塞进一个按对象大小分档的内存池(zsmalloc);读回来时再解压。它并不是无脑压缩,页内容会先走三条分支:
压缩器以"后端"形式挂载,每种算法一个文件,见 drivers/block/zram/ 下的backend_lz4.c、backend_zstd.c等;池分配逻辑在 mm/zsmalloc.c。写路径zram_bvec_write、读路径zram_bvec_read都在 drivers/block/zram/zram_drv.c 里,排障时对照这两个函数看计数从哪来最直观。
mm_stat 的 9 列与 io_stat 的 3 列逐列拆开
用户空间能读到的核心状态就在/sys/block/zramX/下两个文件。mm_stat的 9 列顺序固定,由mm_stat_show()按序 emit,字段定义可对照 Documentation/ABI/testing/sysfs-block-zram:
| 列序 | 字段 | 含义 | 合理取值 / 经验阈值 |
|---|---|---|---|
| 1 | orig_data_size | 当前存入的原始数据量(未压缩口径) | ≤ disksize |
| 2 | compr_data_size | 压缩后实际占用的数据量 | 越小说明压缩越有效 |
| 3 | mem_used_total | 内存池真实占用(含元数据) | 这是 zram 真正吃的 RAM |
| 4 | mem_limit | 人为设定的占用上限,0=不限 | 按需设定 |
| 5 | mem_used_max | 历史峰值占用 | 逼近 disksize 要警惕 |
| 6 | same_pages | 全零页数量 | 占比高=压缩器没白干 |
| 7 | pages_compacted | 碎片整理移动过的对象数 | 持续上涨=池在碎片化 |
| 8 | huge_pages | 当前多页槽位数量 | 反映大对象占比 |
| 9 | huge_pages_since | 累计产生的多页槽位 | 只增不减,看增量 |
io_stat只报块层不统计的部分,3 列依次为 failed_reads、failed_writes、notify_free——后两个是内存池吃紧时池向系统发的"释放通知",它们非零就说明 zram 已经在和分配失败搏斗。读写次数则走通用块层接口,/sys/block/zramX/stat的第 1、5 列即读、写次数。
注意一个常被误读的点:压缩率不是某个现成 sysfs 字段,要自己算:
# 第1列=orig,第2列=compr,第6/8列按每页4KB折算,输出真实压缩比 awk '{r=$1/($2+$6*4096+$8*4096); printf "orig=%.1fM compr=%.1fM ratio=%.2f\n", $1/1048576, $2/1048576, r}' < /sys/block/zram0/mm_stat这个比值长期低于 1.1,基本等于 zram 白开——页还在占 RAM,却多付了压缩 CPU。
观测:从一条 awk 到 Prometheus 面板
手工巡检一行就够:
# 一眼看全:压缩比、池占用、失败计数 paste <(awk '{print $1/($2+$6*4096+$8*4096)}' /sys/block/zram0/mm_stat) <(cat /sys/block/zram0/io_stat)脚本化采集把同一套 awk 塞进 cron,5 分钟一次落盘,顺带做阈值判断:
#!/bin/bash # 采集 zram 关键指标,压缩比低于 1.1 输出告警到 stderr LOG=/var/log/zram_metrics.log awk -v t="$(date +%F_%T)" '{ r = $1 / ($2 + $6 * 4096 + $8 * 4096) print t, $1, $2, $5, r, $7 >> "'"$LOG"'" if (r < 1.1) print "zram0 ratio " r " < 1.1" > "/dev/stderr" }' /sys/block/zram0/mm_stat接入 Prometheus最省事的路线是 node_exporter 的 textfile collector,把指标渲染成 exposition 格式,Grafana 里放三张图:压缩比趋势(低于 1.1 画参考线)、mem_used_total 与 disksize 对比(80% 告警)、failed_writes 增量(非零即红色面板)。采集片段:
# 供 node_exporter textfile_collector 抓取 { awk '{printf "zram_compression_ratio %.4f\n", $1/($2+$6*4096+$8*4096)}' /sys/block/zram0/mm_stat awk '{printf "zram_orig_bytes %.0f\nzram_mem_used_bytes %.0f\n", $1, $3}' /sys/block/zram0/mm_stat awk '{printf "zram_failed_writes_total %.0f\n", $2}' /sys/block/zram0/io_stat } > /var/lib/node_exporter/textfile_collector/zram.prom三类高频异常:比值太低、池子打满、换入偏慢
压缩比趴在 1.1 以下。先确认数据本身的可压缩性:如果 workload 主要是已压缩/加密内容(对象缓存、加密块),低比值是必然,此时调算法收益有限,正确动作是缩disksize(服务器建议物理内存的 25%~50%,嵌入式可放到 100%~200%)甚至弃用 zram。若数据其实可压缩,则换更激进的算法,并用 5.19+ 引入的二次压缩把存量页再压一遍:
# 主算法换成 zstd;再让空闲页用次级算法重压缩,降低存量平均大小 echo zstd > /sys/block/zram0/comp_algorithm echo idle_pages > /sys/block/zram0/recompressmem_used_max 逼近 disksize、failed_writes 上涨。这是池子装不下或碎片化后的典型形态,对应io_stat里 notify_free 也会跟着涨。处置分三步:先看pages_compacted是否持续上涨(碎片信号),是则触发一次整理;再检查有没有设mem_limit兜底;最后如果开了后备盘,让不可压缩页走 writeback 腾池:
# 触发 zsmalloc 碎片整理;写回空闲页到后备盘;重置峰值计数便于观察新趋势 echo 1 > /sys/block/zram0/compact echo 1 > /sys/block/zram0/writeback echo 0 > /sys/block/zram0/mem_used_max换入延迟高但 zram 本身"健康"。这时重点怀疑页其实不在 RAM 里:看后备盘计数文件bd_stat(bd_count、bd_reads、bd_writes),如果 bd_reads 明显增长,说明大量页被 writeback 到了盘上,读回走的是磁盘路径,zram 的快就没了。对策是收紧 writeback 触发(调小writeback_limit、必要时writeback_limit_enable置 0),或把后备盘换成更快的介质。
一个判断收尾
zram 的全部状态可以浓缩成四个视角:压缩器有没有用(压缩比)、池子占了多少(mem_used_total / mem_used_max)、碎片化程度(pages_compacted)、失败与回写(io_stat 与 bd_stat)。日常盯住这四组,90% 的异常都能对号入座。想继续深入,读 Documentation/admin-guide/blockdev/zram.rst 里的完整配置流程,再从 drivers/block/zram/zram_drv.c 的zram_bvec_write顺着计数变量的每个atomic64_inc走一遍,比任何指标手册都清楚。留给读者一个问题:如果换成以不可压缩数据为主的 workload,zram 的账还划得来吗?
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考