背景介绍
- 业务对于服务器成本的要求越来越高。有些服务进程(如场景进程)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开关
1 | echo 1 > /sys/kernel/mm/ksm/run #启动KSM |
- 控制参数
1 | /sys/kernel/mm/ksm/sleep_millisecs #ksmd两次扫描之间的时间间隔,单位毫秒,默认20ms。 |
- 用于监控的参数
1 | /sys/kernel/mm/ksm/full_scans #记录已经执行的全区域扫描次数。 |
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
之前,需对此有一定的了解,对于单进程消耗内存比较大且进程数目集中在某一台机器上的业务建议考虑接入,实际上我们接入的成本换取的收益是可观的。
赞赏一下