KSM应用实践

Posted by 刘勇 on 2020-01-05

背景介绍

  • 业务对于服务器成本的要求越来越高。有些服务进程(如场景进程)load完整的数据需要占用900多M内存,且会运行多个实例(几十个),有时也会使用多个容器共享宿主机内存使用会进一步加剧。此时内存成为了服务器的瓶颈。

KSM原理

  • Kernel Samepage MergingLinux 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域记录。
  • 我们来看一幅图详细了解一下内存的分配过程。image

KSM实现

  • KSM后常驻一个名叫ksmd非实时线程。它会执行ksm.c源码里的ksm_do_scan接口定时扫描被标记为MMF_VM_MERGEABLEmm_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的扫描合并过程。

    image

KSM接入成本

  • 对于需要接入的产品而言,主要的接入成本如下:
    • 需改动代码做适配,主动标记希望合并的数据。
    • 启用后会常驻一个扫描进程ksmd,带来少量的额外CPU消耗。

KSM收益与风险

收益

  • 对于普通进程,每个进程都要加载同样进程数据,一台机器同时打开几十个进程,有较多相同内存数据,KSM会取得良好效果。
  • 内存的节省,对公有云和容器能带来后续的成本收益。

风险

  • 系统的虚拟内存区域vma最大的map数量是有限制的,一旦超出,那么会引发OOM Killer导致进程crash如果调大max_map_count生成过多的vma会产生碎片导致系统的性能下降,我们应该根据自身业务的规模进行合理调整并加上监控预警,具体监控的指标可以参考下面的KSM配置部分。

KSM配置

  • KSM开关
1
2
3
echo 1 > /sys/kernel/mm/ksm/run   #启动KSM
echo 0 > /sys/kernel/mm/ksm/run #关闭KSM
echo 2 > /sys/kernel/mm/ksm/run #停止运行态的KSM并取消合并所有合并页
  • 控制参数
1
2
3
/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表示不允许。
  • 用于监控的参数
1
2
3
4
5
/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之前,需对此有一定的了解,对于单进程消耗内存比较大且进程数目集中在某一台机器上的业务建议考虑接入,实际上我们接入的成本换取的收益是可观的。

推荐阅读



支付宝打赏 微信打赏

赞赏一下