KSM应用实践
原创 lyonger 网易游戏运维平台 2019-08-17
lyonger
18年加入网易,先后负责过多个游戏产品的运维工作,多年运维生涯。负责小游戏CI/CD、事件处理平台开发、游戏Nomad运维模式探索、gitlab平台维护等工作。主要关注Linux性能优化、DevOps、云原生领域。探索和分享是一趟美好的旅程。
背景介绍
业务对于服务器成本的要求越来越高。有些服务进程(如场景进程)load 完整的数据需要占用 900 多 M 内存,且会运行多个实例(几十个),有时也会使用多个容器共享宿主机内存使用会进一步加剧。此时内存成为了服务器的瓶颈。
KSM 原理
Kernel Samepage Merging
是Linux kernel 2.6.32
引入的新特性,最初时为 KVM 虚拟化技术开发的,但对于普通应用程序同样适用。具体参考官方文档。基本原理是定期扫描内存页,将相同的内存页合并,并将其标示为 cow(当需要修改时,copy 新的内存页,再做修改),最终达到节省内存的目的。
我们先了解一下Linux
的内存机制,方便理解KSM
原理。
Linux 内存机制
学过操作系统的同学应该知道Linux
是采用分页的内存机制来管理物理内存。内核采用虚拟内存管理技术为每个进程分配独立的虚拟内存地址空间。而物理内存的分配是由进程去访问虚拟地址时产生缺页异常 (Page Fault) 来触发。
一个进程的虚拟地址空间在内核中用内存描述符struct mm_struct
进行表示,而进程的虚拟地址空间又被划分为多个虚拟内存区域struct vm_area_struct
,简称vma
。另外,进程描述符由struct task_struct
中的mm
域记录。
我们来看一幅图详细了解一下内存的分配过程。
KSM 实现
KSM
后常驻一个名叫ksmd
非实时线程。
它会执行 ksm.c 源码里的ksm_do_scan
接口定时扫描被标记为MMF_VM_MERGEABLE
的mm_struct
[内存描述符],调用cmp_and_merge_page
识别并合并内容完全一样的物理页,扫描的间隔和每次扫描的页数分别由/sys/kernel/mm/ksm/pages_to_scan
、/sys/kernel/mm/ksm/sleep_millisecs
控制。
用户层可以通过系统调用madvise(addr,length,MADV_MERGEABLE)
对一块页对齐的内存标记为可用于 KSM 合并。
此外由于madvise
系统调用会通过内核源码 madvise.c 里的madvise_behavior
接口对内存区域 vma 中的内存进行标记,如果该区间和周围的内存区间标记不同,那么会分配新的vma
,而内核对进程持有的vma
是有限制的,分配的 vma 数目必须小于/proc/sys/vm/max_map_count
里的限制,一旦超出,那么会引发OOM Killer
导致进程crash
。
如果调大max_map_count
产生过多的vma
会导致系统的性能下降,我们应该根据自身业务的规模进行合理调整,同时有必要加上监控预警。
我们来看一幅图详细了解一下 KSM 的扫描合并过程。
KSM 接入成本
对于需要接入的产品而言,主要的接入成本如下:
-
需改动代码做适配,主动标记希望合并的数据。
-
启用后会常驻一个扫描进程 ksmd,带来少量的额外 CPU 消耗。
KSM 收益与风险
收益
-
对于普通进程,每个进程都要加载同样进程数据,一台机器同时打开几十个进程,有较多相同内存数据,
KSM
会取得良好效果。 -
内存的节省,对公有云和容器能带来后续的成本收益。
风险
系统的虚拟内存区域vma
最大的map
数量是有限制的,一旦超出,那么会引发OOM Killer
导致进程crash
。
如果调大max_map_count
生成过多的vma
会产生碎片导致系统的性能下降,我们应该根据自身业务的规模进行合理调整并加上监控预警,具体监控的指标可以参考下面的KSM配置部分。
KSM 配置
-
KSM 开关
echo 1 > /sys/kernel/mm/ksm/run #启动KSM
echo 0 > /sys/kernel/mm/ksm/run #关闭KSM
echo 2 > /sys/kernel/mm/ksm/run #停止运行态的KSM并取消合并所有合并页
-
控制参数
/sys/kernel/mm/ksm/sleep_millisecs #ksmd两次扫描之间的时间间隔,单位毫秒,默认20ms。
/sys/kernel/mm/ksm/pages_to_scan #控制一次扫描处理的页数,默认为100。
/sys/kernel/mm/ksm/merge_across_nodes #控制是否允许不同的NUMA节点之间进行页合并,默认允许,写入0表示不允许。
-
用于监控的参数
/sys/kernel/mm/ksm/full_scans #记录已经执行的全区域扫描次数。
/sys/kernel/mm/ksm/pages_shared #记录稳定树节点数,即共享的物理页数。
/sys/kernel/mm/ksm/pages_sharing #记录被共享的物理页数,通过此可计算节省的物理内存。
/sys/kernel/mm/ksm/pages_unshared #记录不稳定树的节点树,即未共享的物理页数。
/sys/kernel/mm/ksm/pages_volatile #记录频繁改变的物理页数。
KSM 应用实例
空载试验
-
在 4 台宿主上,共启动 8 个容器,每个容器部署 32 个进程,宿主机内存对比如下:
机器IP | 总内存大小 | 未启动KSM时内存占用 | 启动KSM时内存占用 |
---|---|---|---|
10.203.10.183 | 64G | 35.4688G | 20.6592G |
10.203.10.185 | 64G | 35.4816G | 20.6592G |
10.203.10.184 | 64G | 35.4048G | 20.4544G |
10.203.10.186 | 64G | 35.4432G | 20.48G |
10.203.10.165 | 64G | 34.8032G | 19.5328G |
10.203.10.167 | 64G | 34.8032G | 19.5584G |
10.203.10.164 | 64G | 34.816G | 19.5456G |
10.203.10.166 | 64G | 34.8032G | 19.5328G |
-
如上表格可知,启动 KSM,内存使用减少了大概
15G
,减幅达到23%
。
运行试验
-
在一个宿主上,启动 2 个容器,每个容器部署 32 个进程,其中一个容器有 9600 个用户在不停的请求,一个容器有 800 个用户在不停的进行请求,请求人数较多的容器数据对比如下:
机器IP | 总内存大小 | 未启动KSM时内存占用 | 启动KSM时内存占用 |
---|---|---|---|
10.203.10.183 | 64G | 44.1856G | 29.3632G |
10.203.10.186 | 64G | 44.1472G | 29.2864G |
10.203.10.184 | 64G | 44.0832G | 29.1584G |
10.203.10.185 | 64G | 44.1984G | 29.1968G |
-
如上表格可知,启动 KSM,内存中战斗最终减低了大概
15G
,减幅达23%
。
总结
-
使用
KSM
之前,需对此有一定的了解,对于单进程消耗内存比较大且进程数目集中在某一台机器上的业务建议考虑接入,实际上我们接入的成本换取的收益是可观的。
特别鸣谢
-
特别感谢 Kuso 同学对于本文的贡献和支持。
往期精彩
NEW
﹀
﹀
﹀
S3 的中文编码问题及修复方案
通用实时日志分类统计实践
从清档需求谈谈 Redis 二级索引的使用
Swap 与 Swappiness
网易游戏海外 AWS 动态伸缩实践