操作系统内功 · 第 47 篇|zswap 与 KSM:内存压缩与同页合并的工程取舍

与第 37 篇分工:从交换选型到压缩去重

第 37 篇讲的是交换空间本身的选型:swap 分区还是交换文件、zram 要不要开、swappiness 怎么调。这一篇往深处再走一层,讲两条更精细的省内存技术路线:压缩与去重。它们解决的是同一个焦虑——物理内存不够用——但手法完全不同。压缩路线承认「这些页必须存在」,只是把它们塞进更小的空间,用 CPU 时间换内存密度;去重路线则盯住一个统计事实:大规模同构负载里存在大量内容完全相同的页,一份就够了。

zswap 与 KSM 分别是这两条路线的官方代表。zswap 挂在换出路径上,是「页离开内存之前的压缩中转池」;KSM 是一个后台扫描守护线程,专职发现并合并内容相同的匿名页。两者的启用收益与代价模型差异极大,也因此适用场景几乎不重叠:zswap 主要救内存紧张但不想承受磁盘交换延迟的主机,KSM 主要救跑着一堆相似虚拟机或相似进程的宿主机。把它们当成同一类开关去调,是运维事故的常见起点。

先建立一个总账视角。压缩的收益上限由压缩比决定,常见算法对匿名页能拿到两到三倍的空间节省,代价是压缩解压的 CPU 与池子本身的内存管理开销;去重的收益上限由重复率决定,几百台同版本系统的虚拟机宿主机上重复页占比可以相当可观,而异构负载上可能趋近于零,代价却是持续的扫描 CPU 与写保护缺页延迟。所以两者的第一问都是「我的负载里有多少可省的东西」,而不是「内核功能开不开」。

zswap:交换路径上的压缩中转池

zswap 不是一个块设备,而是换出路径上的一个拦截层。当内存回收决定把某个匿名页换出时,zswap 先把它截住:页经过压缩算法压缩,塞进内核内存里的一个专用池子,登记在按地址组织起来的查找结构里。只有当这个池子到达容量上限、或者池中的冷页被再次挤出时,才解压回写真正的 swap 设备。换入时走相反路径:找到池中条目,解压,重新映射回进程地址空间。整个过程对应用完全透明,应用看到的仍然是普通的 swap 语义。

关键参数集中在 /sys/module/zswap/parameters 目录下。max_pool_percent 控制压缩池最多占用物理内存的百分比,默认两成左右,这是 zswap 区别于「无限压缩」的护栏——池子有上界,内存占用可预期;compressor 选择压缩算法,lzo、lz4、zstd 等按内核编译情况可选,压缩比与速度各有取舍;zpool 选择池子的分配器实现,不同实现决定一个物理页里能塞几个压缩后的页,密度与碎片率不同;same_filled_pages_enabled 开启后,全零页直接记账不压缩,这类页在刚初始化的大缓冲区里比例不低,白捡的收益。writeback 开关则决定池满后是否允许继续向磁盘下沉。

zswap 与 zram 的取舍是这一篇绕不开的比较。zram 是一个压缩内存块设备,swap 设备本身就是内存,数据永远不下盘;zswap 只是前置层,身后必须站着真正的 swap 设备,冷页能继续下沉。无盘主机或者不信任磁盘延迟的场景选 zram;有盘、想要「热页留在内存里压缩、冷页下沉到盘」分层结构的选 zswap。两者都服务于同一目标——把交换延迟从磁盘量级拉回内存量级——但部署形态不同,选错会导致 swap 设备缺失时 zswap 完全不生效这类迷惑现场。多数发行版对两者的默认态各不相同,上线前先确认当前内核的实际配置,别凭印象。

zswap 的风险同样要记账。压缩解压吃 CPU,内存型负载在频繁换入换出时会看到 CPU 占用抬升;池子上限设置过高时,压缩池本身会挤占本已紧张的内存,形成「为了省内存反而更缺内存」的悖论;另外压缩池不参与常规页缓存回收语义,观测时要在内存账本里为它单列一项,否则容量规划会漏算一块。存算分离时代很多主机干脆不配 swap,这类主机 zswap 与 zram 都无从谈起,扩内存或者降配才是正解。

压缩算法的选择值得单独说两句。lzo 胜在快,压缩解压开销最低,适合 CPU 已经吃紧的主机;lz4 在速度与压缩比之间更平衡,是多数场景的稳妥选择;zstd 压缩比最高,换出的页更省内存,但 CPU 开销也最高,适合「内存极缺、CPU 相对富余」的环境。算法可以在线切换,切换只影响之后的压缩操作,池中旧数据按原格式保留。选择时先想清楚瓶颈在哪一侧:内存瓶颈压倒一切选 zstd,CPU 余量不足选 lzo 或 lz4,拿不准就压测三种算法下的换入换出延迟与 CPU 曲线,用数据说话。

KSM:同内容匿名页的合并机器

KSM 的机制是一个持续运转的扫描循环。内核守护线程 ksmd 周期性地扫描被标记为「可合并」的匿名页区域,为每个候选页计算一个稳定的校验值并组织进红黑树,两轮扫描确认内容稳定后,若发现树中存在内容完全相同的页,就把这些进程的页表项改写为指向同一个只读物理页,原页释放。之后任何进程写这个共享页,都会触发写保护缺页,内核复制一份私有副本再执行写入——就是熟悉的写时复制语义,只不过这次发生在进程之间。

可合并区域的圈定有两种方式。应用可以调用 madvise 配合 MERGEABLE 建议精准圈定自己的某段内存;也可以由系统把运行模式设为全局,让 KSM 处理所有符合条件的匿名页。虚拟化场景里,QEMU 这类管理程序会在虚拟机进程的内存区域上代为打标,宿主机不需要人工介入。扫描节奏由 sysfs 下的 pages_to_scan 与 sleep_millisecs 控制:每轮扫多少页、轮间隔多少毫秒,直接决定合并速度与 CPU 代价的兑换比例。NUMA 机器上还有 merge_across_nodes 开关:跨节点合并省内存更多,但合并后的页可能远离访问者,破坏局部性,内存带宽敏感的负载要掂量。

代价与风险是 KSM 落地讨论的重心。第一是 ksmd 的 CPU 消耗:扫描密度调高时,单核百分之几十的占用并不罕见,而这部分开销是持续性的。第二是延迟毛刺:写保护缺页让原本简单的内存写变成「缺页、复制、再写」的慢路径,对延迟敏感的进程是隐形地雷。第三是统计失真:页被合并后,各进程的内存账本与真实物理占用脱节,容量监控与配额计算都可能被误导。第四是安全面:跨租户合并相同页会打开侧信道探测的口子——攻击者可以通过测量写入延迟推断别的租户是否加载了特定内容,因此多租户公有云大多直接禁用 KSM,这不是性能问题而是隔离问题。

数据库是 KSM 的典型反面场景。数据页几乎不含重复内容(每页都是不同的行与索引),扫描是纯浪费,写保护缺页却货真价实地打击 P99。MySQL 与 PostgreSQL 社区的一致建议都是关闭 KSM。KSM 的主场始终是同构虚拟机密度、同版本 JVM 多副本这类「内容高度雷同」的负载——判定标准不是「内存紧张」,而是「重复率高」。

KSM 与其他内存机制的互动也要有数。频繁的合并拆分会干扰内存规整的节奏;透明巨页与 KSM 在匿名页上的目标相互冲突——一个要把页变大,一个要把页变少——两者同时激进启用时行为难以预测,同机共存时都建议保守配置。另外 KSM 的合并粒度是单页,堆里大量「内容相似但不完全相同」的页它无能为力,不要把去重收益想象成数据去重软件那样的压缩率,它是逐字节完全相同才有的一等公民。

zram 对照与场景决策

把三个机制放进一张对照表里看。zram:swap 设备即压缩内存,无盘可用,适合云主机与嵌入式;zswap:压缩池加磁盘分层,适合有盘且想分层回收的主机;KSM:内容去重,收益完全取决于重复率,适合虚拟化宿主与同构多副本。三者可以共存于同一台机器,但要明白它们作用于不同路径:zram 与 zswap 在换出路径上,KSM 在常驻内存上,互不替代。

虚拟化宿主机的经典配方是 KSM 配合适度超售:几百台相同基础镜像的虚拟机,合并收益可观,宿主机整体内存超售率可以安全上调,前提是 ksmd 的 CPU 预算已经规划、多租户隔离要求已经排除。数据库专用主机则是另一个极端:KSM 关闭、THP 谨慎、zram 或 zswap 只在内存扩容前的过渡期作为权宜之计——压缩是省内存的手段,不是省预算的借口,长期靠压缩续命的数据库迟早要还 CPU 的债。

容器宿主机处于中间地带。同镜像多副本的无状态服务,理论上有去重空间,但容器负载动态伸缩、页生命周期短,KSM 的收敛速度常常追不上变更频率,全局开启性价比低;真有确定性收益的是那类常驻的大内存守护进程集群,用 madvise 定点圈定比全局扫描务实。结论只有一句话:先测量重复率,再决定开关。用 内存账本前后对比 与 ksmd CPU 曲线验证收益,比任何文档都可信。

观测口径最后补齐。zswap 看参数目录下的池子水位、压缩页数与回写页数三个计数,回写频繁说明池子不够大或冷热分离不理想;zram 看块设备统计里的压缩比与内存占用,压缩比长期低于预期说明负载的数据形态不适合压缩(随机数据、已压缩内容再压一遍收益趋零);KSM 看已合并页数与扫描页数的比值,以及守护线程的 CPU 时间序列。三项机制都有「计数器说话」的观测传统,先看数字再调参数,是这一篇最想留下的工作习惯。

实操清单与常见误区

运维清单。内存紧张但无 swap 的主机:优先评估 zram,压测换入换出路径的 CPU 抬升;有盘主机启用 zswap:先确认 swap 设备存在,池子上限从默认值起步,compressor 按压测选择,观察池子水位与回写频率。虚拟化宿主:KSM 扫描参数保守起步,监控 ksmd CPU 与合并页数曲线,多租户环境一律禁用。数据库主机:KSM 与 THP 关闭,压缩路线只作过渡。

常见误区。误区一:把 zswap 当成独立的 swap 替代品——它是前置层,身后没有 swap 设备就不工作。误区二:KSM 开了就有收益——重复率低的负载上它只是持续的 CPU 税。误区三:压缩池上限拉满省得更多——池子本身占内存,上限过高反而加剧紧张。误区四:多租户云上开 KSM 省钱——侧信道风险属于安全红线,省下的内存抵不过隔离性损失。误区五:只看内存指标不看 CPU 曲线——压缩与扫描的代价都记在 CPU 上,账要一起算。

再补一个容量视角的提醒:压缩与去重改变的是内存的「表观占用」,不改变工作集的真实大小。容量规划时按未压缩口径估算扩容拐点,压缩参数只当作缓冲带——把「池子水位见顶且回写频繁」「守护线程 CPU 超预算」「合并率持续走低」三个信号设成告警,它们各自是三种机制走向失效的前兆,提前一周看到,就有一周的从容。

小结

zswap 与 KSM 是内存紧张的两种工程答案:前者用 CPU 换密度,在换出路径上建压缩中转池;后者用扫描换去重,把内容相同的匿名页合并成一份。它们的收益模型完全不同——压缩看压缩比,去重看重复率——所以适用场景几乎不重叠。

选型纪律是先测量后启用:压缩路线压测 CPU 抬升与池子水位,去重路线先确认负载里真有可观的比例可合并。数据库对两者都保持距离,虚拟化宿主与同构副本集群才是它们的回报区。

内存优化的终点永远是容量规划:压缩与去重买来的是时间,不是空间的消失。用它们争取的窗口,去完成该做的扩容与架构调整,才是这两项技术的正确用法。

← 返回资讯列表

读者留言

COMMENTS 暂无
仅本站原创文章开放留言 · 请勿留下手机号、邮箱等个人信息

还没有留言,来说第一句?