文章目录

  • 1 问题分析
  • 2 难道是zone_reclaim_mode?
  • 2.1 NUMA是什么?
  • 2.2 zone_reclaim_mode=0管用么?
  • 3 内存回收
  • 3.1 内存回收的触发路径(min_free_kbytes)
  • 3.2 Linux内存回收对象主要分为两种(swappiness)
  • 4 实际情况
  • 4.1 优化前的情况
  • 4.2 优化后
  • 4.3 优化后的效果
  • 5 参考文档
  • 技术交流


1 问题分析

CleverCode公司部署的redis是集群模式,使用的是3主3从,部分redis集群直接部署在物理机上面。为了充分利用物理机资源。物理机上面还跑着fastdfs进程。这样redis使用物理机的内存。fastdfs使用物理机的磁盘。
但是跑了一段时间后发现redis经常会有连接超时情况。如下出现Connection timed out [tcp://10.x.x.74:6416],
Error while reading line from the server. [tcp://10.x.x.74:6392]

某一天的报错情况:

redis写入卡死 redis读写超时_redis写入卡死


很多天的报错情况:

redis写入卡死 redis读写超时_redis写入卡死_02


从zabbix里面查找内存的使用监控:发现11月17号redis开始使用后,Mem_used会周期性的接近94.37G(物理内存最大值)。每次Mem_use接近到达94.37G后,发现会有一次内存回收释放操作,Mem_cached减少,Mem_free开始增加。

redis写入卡死 redis读写超时_内存回收_03

2 难道是zone_reclaim_mode?

2.1 NUMA是什么?

经过一顿排查,发现我们系统内核参数里面 /etc/sysctl.conf 配置的vm.zone_reclaim_mode=1

redis写入卡死 redis读写超时_redis写入卡死_04


vm.zone_reclaim_mode=1是什么东西?这个参数是控制NUMA开关的。NUMA又是什么?NUMA(Non-Uniform Memory Access)是相对UMA来说的,两者都是CPU的设计架构,早期CPU设计为UMA结构,如下图(图片来自网络)所示:

redis写入卡死 redis读写超时_redis写入卡死_05


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

redis写入卡死 redis读写超时_内存回收_06


这种架构可以很好解决UMA的问题,即不同CPU有专属内存区,为了实现CPU之间的”内存隔离”,还需要软件层面两点支持:

内存分配需要在请求线程当前所处CPU的专属内存区域进行分配。如果分配到其他CPU专属内存区,势必隔离性会受到一定影响,并且跨越总线的内存访问性能必然会有一定程度降低。

另外,一旦local内存(专属内存)不够用,优先淘汰local内存中的内存页,而不是去查看远程内存区是否会有空闲内存借用。

这样实现,隔离性确实好了,但问题也来了:NUMA这种特性可能会导致CPU内存使用不均衡,部分CPU专属内存不够使用,频繁需要回收,进而可能发生大量swap,系统响应延迟会严重抖动。而与此同时其他部分CPU专属内存可能都很空闲。这就会产生一种怪现象:使用free命令查看当前系统还有部分空闲物理内存,系统却不断发生swap,导致某些应用性能急剧下降。见叶金荣老师的MySQL案例分析:《找到MySQL服务器发生SWAP罪魁祸首》。

所以,对于小内存应用来讲,NUMA所带来的这种问题并不突出,相反,local内存所带来的性能提升相当可观。但是对于数据库这类内存大户来说,NUMA默认策略所带来的稳定性隐患是不可接受的。因此数据库们都强烈要求对NUMA的默认策略进行改进,有两个方面可以进行改进:

  • 将内存分配策略由默认的亲和模式改为interleave模式,即会将内存page打散分配到不同的CPU
    zone中。通过这种方式解决内存可能分布不均的问题,一定程度上缓解上述案例中的诡异问题。对于MongoDB来说,在启动的时候就会提示使用interleave内存分配策略.
  • 改进内存回收策略:此处终于请出今天的第三个主角参数zone_reclaim_mode,这个参数定义了NUMA架构下不同的内存回收策略,可以取值0/1/3/4,其中0表示在local内存不够用的情况下可以去其他的内存区域分配内存;1表示在local内存不够用的情况下本地先回收再分配;3表示本地回收尽可能先回收文件缓存对象;4表示本地回收优先使用swap回收匿名内存。可见,HBase推荐配置zone_reclaim_mode=0一定程度上降低了swap发生的概率。

NUMA部分详细的说明可以查看:《Linux Swap的那些事》https://www.jianshu.com/p/73847b688728。

2.2 zone_reclaim_mode=0管用么?

发现NUMA这个特征之后发现我们机器,果然使用这个zone_reclaim_mode=1这个配置,赶紧联系运维注释掉/etc/sysctl.conf,#vm.zone_reclaim_mode=1,然后sysctl -p让配置生效。

redis写入卡死 redis读写超时_redis写入卡死_07


然后观察一段时间后。。。。。。问题依然存在。所以根本问题不是NUMA架构导致的。

redis写入卡死 redis读写超时_架构师_08

3 内存回收

3.1 内存回收的触发路径(min_free_kbytes)

Linux会在两种场景下触发内存回收,一种是在内存分配时发现没有足够空闲内存时会立刻触发内存回收;一种是开启了一个守护进程(swapd进程)周期性对系统内存进行检查,在可用内存降低到特定阈值之后主动触发内存回收。第一种场景没什么可说,来重点聊聊第二种场景,如下图所示:

redis写入卡死 redis读写超时_redis写入卡死_09

watermark[min] = min_free_kbytes
watermark[low] = watermark[min] * 5 / 4 = min_free_kbytes * 5 / 4
watermark[high] = watermark[min] * 3 / 2 = min_free_kbytes * 3 / 2
watermark[high] - watermark[low] = watermark[low] - watermark[min] = min_free_kbytes / 4

可见,LInux的这几个水位线与参数min_free_kbytes密不可分。min_free_kbytes对于系统的重要性不言而喻,既不能太大,也不能太小。

min_free_kbytes如果太小,[min,low]之间水位的buffer就会很小,在kswapd回收的过程中一旦上层申请内存的速度太快(典型应用:数据库,fastdfs),就会导致空闲内存极易降至watermark[min]以下,此时内核就会进行direct reclaim(直接回收),直接在应用程序的进程上下文中进行回收,再用回收上来的空闲页满足内存申请,因此实际会阻塞应用程序,带来一定的响应延迟。

当然,min_free_kbytes也不宜太大,太大一方面会导致应用程序进程内存减少,浪费系统内存资源,另一方面还会导致kswapd进程花费大量时间进行内存回收。

3.2 Linux内存回收对象主要分为两种(swappiness)

  1. 文件缓存,这个容易理解,为了避免文件数据每次都要从硬盘读取,系统会将热点数据存储在内存中,提高性能。如果仅仅将文件读出来,内存回收只需要释放这部分内存即可,下次再次读取该文件数据直接从硬盘中读取即可(类似HBase文件缓存)。那如果不仅将文件读出来,而且对这些缓存的文件数据进行了修改(脏数据),回收内存就需要将这部分数据文件写会硬盘再释放(类似MySQL文件缓存)。
  2. 匿名内存,这部分内存没有实际载体,不像文件缓存有硬盘文件这样一个载体,比如典型的堆、栈数据等。这部分内存在回收的时候不能直接释放或者写回类似文件的媒介中,这才搞出来swap这个机制,将这类内存换出到硬盘中,需要的时候再加载出来。

既然有两类内存可以被回收,那么在这两类内存都可以被回收的情况下,Linux到底是如何决定到底是回收哪类内存呢?还是两者都会被回收?这里就牵出来了我们第二个关心的参数:swappiness,这个值用来定义内核使用swap的积极程度,值越高,内核就会积极地使用swap,值越低,就会降低对swap的使用积极性。该值取值范围在0~100,默认是60。这个swappiness到底是怎么实现的呢?具体原理很复杂,简单来讲,swappiness通过控制内存回收时,回收的匿名页更多一些还是回收的文件缓存更多一些来达到这个效果。swappiness等于100,表示匿名内存和文件缓存将用同样的优先级进行回收,默认60表示文件缓存会优先被回收掉,至于为什么文件缓存要被优先回收掉,大家不妨想想(回收文件缓存通常情况下不会引起IO操作,对系统性能影响较小)。对于数据库来讲,swap是尽量需要避免的,所以需要将其设置为0。此处需要注意,设置为0并不代表不执行swap哦!

4 实际情况

根据第3章节的内存回收的知识,我们来看看我们这两个配置min_free_kbytes和swappiness。特别是min_free_kbytes如果太小,此时内核就会进行direct reclaim(直接回收),直接在应用程序的进程上下文中进行回收,再用回收上来的空闲页满足内存申请,因此实际会阻塞应用程序,带来一定的响应延迟。

4.1 优化前的情况

min_free_kbytes 设置的只有90M

cat /proc/sys/vm/min_free_kbytes

redis写入卡死 redis读写超时_架构师_10


swappiness 设置的是60。默认情况。

cat /proc/sys/vm/swappiness

redis写入卡死 redis读写超时_架构师_11

4.2 优化后

min_free_kbytes改成了2G

[logdev@g4-xxxxx.ops.prod.idc1 ~]$ cat /proc/sys/vm/min_free_kbytes
2048000
[logdev@g4-xxxxx.ops.prod.idc1 ~]$

swappiness变成了10,就会降低对swap的使用积极性,尽可能的使用物理内存。

[logdev@g4-xxxxx.ops.prod.idc1 ~]$ cat /proc/sys/vm/swappiness
10
[logdev@g4-xxxxx.ops.prod.idc1 ~]$

4.3 优化后的效果

发现Mem_free一直在2G左右。也没有出现连接超时的情况。

redis写入卡死 redis读写超时_redis写入卡死_12

5 参考文档

《numa详解》:

《Linux Swap的那些事》:https://www.jianshu.com/p/73847b688728 这篇文章解释的很清楚。

《找到MySQL服务器发生SWAP罪魁祸首》:https://mp.weixin.qq.com/s?__biz=MjM5NzAzMTY4NQ==&mid=2653929537&idx=1&sn=3fad622f505175d9ca8399cfb14b925f&chksm=bd3b5a2b8a4cd33d0bd75078614106ee4065b732684fb949d7477f45af56ae8be0a472fc75b1#rd

《Linux内存管理学习笔记–物理内存分配》:https://www.xuebuyuan.com/3214566.html

https://mp.weixin.qq.com/s?src=11&timestamp=1575973816&ver=2026&signature=NNSmXPLZkTSUjMq8EWEemC4zycdLwidcJ2PPs07rKVSCDNNKu64xnoMHtN0XD7q-TvGwhJarZg89Osu-bWWlc81958vwSMcR1ymnAZ*BvYf9JjOt6CbczjlxbE3y7SKZ&new=1

《linux 的swap、swappiness及kswapd原理【转】》https://cloud.tencent.com/developer/article/1512940

《Linux内存管理回收机制》:

《Linux学习-内存管理篇(六)-内存回收(lru链表)》: