为了缓解多核CPU读取同一块内存所遇到的通道瓶颈问题,芯片工程师又设计了NUMA结构,如下图(图片来自网络)所示:

  这种架构可以很好解决UMA的问题,即不同CPU有专属内存区,为了实现CPU之间的”内存隔离”,还需要软件层面两点支持:
  1. 内存分配需要在请求线程当前所处CPU的专属内存区域进行分配。如果分配到其他CPU专属内存区,势必隔离性会受到一定影响,并且跨越总线的内存访问性能必然会有一定程度降低。
  2. 另外,一旦local内存(专属内存)不够用,优先淘汰local内存中的内存页,而不是去查看远程内存区是否会有空闲内存借用。
  这样实现,隔离性确实好了,但问题也来了:NUMA这种特性可能会导致CPU内存使用不均衡,部分CPU专属内存不够使用,频繁需要回收,进而可能发生大量swap,系统响应延迟会严重抖动。而与此同时其他部分CPU专属内存可能都很空闲。这会产生一种怪现象:使用free命令查看当前系统还有部分空闲物理内存,系统却不断发生swap,导致某些应用性能急剧下降。见叶金荣老师的MySQL案例分析:《找到MySQL服务器发生SWAP罪魁祸首》。
  所以,对于小内存应用来讲,NUMA所带来的这种问题并不突出,相反,local内存所带来的性能提升相当可观。但是对于数据库这类内存大户来说,NUMA默认策略所带来的稳定性隐患是不可接受的。因此数据库们都强烈要求对NUMA的默认策略进行改进,有两个方面可以进行改进:
  1. 将内存分配策略由默认的亲和模式改为interleave模式,即会将内存page打散分配到不同的CPU zone中。通过这种方式解决内存可能分布不均的问题,一定程度上缓解上述案例中的诡异问题。对于MongoDB来说,在启动的时候会提示使用interleave内存分配策略:
  WARNING: You are running on a NUMA machine.
  We suggest launching mongod like this to avoid performance problems:
  numactl –interleave=all mongod [other options]
   2. 改进内存回收策略:此处终于请出的第三个主角参数zone_reclaim_mode,这个参数定义了NUMA架构下不同的内存回收策略,可以取值0/1/3/4,其中0表示在local内存不够用的情况下可以去其他的内存区域分配内存;1表示在local内存不够用的情况下本地先回收再分配;3表示本地回收尽可能先回收文件缓存对象;4表示本地回收优先使用swap回收匿名内存。可见,HBase推荐配置zone_reclaim_mode=0一定程度上降低了swap发生的概率。
  不都是swap的事
  至此,我们探讨了三个与swap相关的系统参数,并且围绕Linux系统内存分配、swap以及NUMA等知识点对这三个参数进行了深入解读。除此之外,对于数据库系统来说,还有两个非常重要的参数需要特别关注:
  1. IO调度策略:这个话题网上有很多解释,在此并不打算详述,只给出结果。通常对于sata盘的OLTP数据库来说,deadline算法调度策略是优的选择。
  2. THP(transparent huge pages)特性关闭。THP特性笔者曾经疑惑过很久,主要疑惑点有两点,其一是THP和HugePage是不是一回事,其二是HBase为什么要求关闭THP。经过前前后后多次查阅相关文档,终于找到一些蛛丝马迹。这里分四个小点来解释THP特性:
  (1)什么是HugePage?
  网上对HugePage的解释有很多,大家可以检索阅读。简单来说,计算机内存是通过表映射(内存索引表)的方式进行内存寻址,目前系统内存以4KB为一个页,作为内存寻址的小单元。随着内存不断增大,内存索引表的大小将会不断增大。一台256G内存的机器,如果使用4KB小页, 仅索引表大小要4G左右。要知道这个索引表是必须装在内存的,而且是在CPU内存,太大会发生大量miss,内存寻址性能会下降。
  HugePage是为了解决这个问题,HugePage使用2MB大小的大页代替传统小页来管理内存,这样内存索引表大小可以控制的很小,进而全部装在CPU内存,防止出现miss。
  (2)什么是THP(Transparent Huge Pages)?
  HugePage是一种大页理论,那具体怎么使用HugePage特性呢?目前系统提供了两种使用方式,其一称为Static Huge Pages,另一种是Transparent Huge Pages。前者根据名称可以知道是一种静态管理策略,需要用户自己根据系统内存大小手动配置大页个数,这样在系统启动的时候会生成对应个数的大页,后续将不再改变。而Transparent Huge Pages是一种动态管理策略,它会在运行期动态分配大页给应用,并对这些大页进行管理,对用户来说完全透明,不需要进行任何配置。另外,目前THP只针对匿名内存区域。
  (3)HBase(数据库)为什么要求关闭THP特性?
  THP是一种动态管理策略,会在运行期分配管理大页,因此会有一定程度的分配延时,这对追求响应延时的数据库系统来说不可接受。除此之外,THP还有很多其他弊端,可以参考这篇文章《why-tokudb-hates-transparent-hugepages》
  (4)THP关闭/开启对HBase读写性能影响有多大?
  为了验证THP开启关闭对HBase性能的影响到底有多大,本人在测试环境做了一个简单的测试:测试集群仅一个RegionServer,测试负载为读写比1:1。THP在部分系统中为always以及never两个选项,在部分系统中多了一个称为madvise的选项。可以使用命令 echo never/always > /sys/kernel/mm/transparent_hugepage/enabled 来关闭/开启THP。测试结果如下图所示:

  如上图,TPH关闭场景下(never)HBase性能优,比较稳定。而THP开启的场景(always),性能相比关闭的场景有30%左右的下降,而且曲线抖动很大。可见,HBase线上切记要关闭THP。
  总结
  任何数据库系统的性能表现都与诸多因素相关,这里面有数据库本身的各种因素,比如数据库配置、客户端使用、容量规划、表scheme设计等,除此之外,基础系统对其的影响也至关重要,比如操作系统、JVM等。很多时候数据库遇到一些性能问题,左查右查都定位不了具体原因,这个时候要看看操作系统的配置是否都合理了。本文从HBase官方文档要求的几个参数出发,详细说明了这些参数的具体意义。内容涉及相对比较多,有兴趣的同学可以详细查看文章后参考文章。