咱们公司的基础架构部有个云 Redis 平台,其中 Redis 实例在申请的时候能够自由选择须要的内存的大小。而后就引发了我的一个思考,Redis 单实例内存最大申请到多大比拟适合?假如母机是 64GB 内存的物理机,如果不思考 CPU 资源的的节约,我是否能够开一个 50G 的 Redis 实例?
于是我在 Google 上各种搜寻,探讨这个问题的人仿佛不多。找到惟一感觉靠谱点的答案,那就是单过程调配的内存最好不要超过一个 node 里的内存总量,否则 linux 当该 node 里的内存调配光了的时候,会在本人 node 里动用硬盘 swap,而不是其它 node 里申请。这即便所谓的 numa 陷阱,当 Redis 进入这种状态后会导致性能急剧下降(不只是 redis,所有的内存密集型利用如 mysql,mongo 等都会有相似问题)。
看起来这个解释十分有说服力。于是乎,我就想亲手捕获一次 NUMA 陷阱,看看这个家伙到底什么样。
先聊聊 QPI 与 NUMA
最早在 CPU 都是单核的时候,用的总线都是 FSB 总线。经典构造如下图:
到来起初 CPU 的开发者们发现 CPU 的频率曾经靠近物理极限了,没法再有更大程度的进步了。在 2003 年的时候,CPU 的频率就曾经达到 2 个多 GB,甚至 3 个 G 了。当初你再来看明天的 CPU,根本也还是这个频率,没提高多少。摩尔定律生效了,或者说是向另外一个方向倒退了。那就是多核化、多 CPU 化。
刚开始核不多的时候,FSB 总线勉强还能够撑持。然而随着 CPU 越来越多,所有的数据 IO 都通过这一条总线和内存叫唤数据,这条 FSB 就成为了整个计算机系统的瓶颈。举个北京的例子,这就好比进回龙观的京藏高速,刚开始回龙观人口不多的时候,这条高速承载没问题。然而当初回龙观汇集了几十万人了,“总线”还仅有这一条,未免效率太低。
CPU 的设计者们很快扭转了本人的设计,引入了 QPI 总线,相应的 CPU 的构造就叫 NMUA 架构。下图直观了解
话说 NUMA 陷阱
NUMA 陷阱指的是引入 QPI 总线后,在计算机系统里可能会存在的一个坑。大抵的意思就是如果你的机器关上了 numa,那么你的内存即便在短缺的状况下,也会应用磁盘上的 swap,导致性能低下。起因就是 NUMA 为了高效,会仅仅只从你的以后 node 里分配内存,只有以后 node 里用光了(即便其它 node 还有),也依然会启用硬盘 swap。
当我第一次据说到这个概念的时候,不禁感叹我运气好,我的 Redis 实例貌似素来没有掉进这个陷阱里过。那为了当前也别栽坑,连忙去理解了下我的机器的 numa 状态:
# numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 12 13 14 15 16 17
node 0 size: 32756 MB
node 0 free: 19642 MB
node 1 cpus: 6 7 8 9 10 11 18 19 20 21 22 23
node 1 size: 32768 MB
node 1 free: 18652 MB
node distances:
node 0 1
0: 10 21
1: 21 10
下面后果阐明咱们有两个 node,node0 和 node1,别离有 12 个外围,各有 32GB 的内存。
再看 zone_reclaim_mode,它用来治理当一个内存区域 (zone) 外部的内存耗尽时,是从其外部进行内存回收还是能够从其余 zone 进行回收的选项:
- 0 敞开 zone_reclaim 模式,能够从其余 zone 或 NUMA 节点回收内存
- 1 关上 zone_reclaim 模式,这样内存回收只会产生在本地节点内
- 2 在本地回收内存时,能够将 cache 中的脏数据写回硬盘,以回收内存
- 4 在本地回收内存时,示意能够用 Swap 形式回收内存
# cat /proc/sys/vm/zone_reclaim_mode
1
额,好吧。我的这台机器上的 zone_reclaim_mode 还真是 1,只会在本地节点回收内存。
实际捕获 numa 陷阱得逞
那我的好奇心就来了,既然我的单个 node 节点只有 32G,那我部署一个 50G 的 Redis,给它填满数据试试到底会不会产生 swap。
试验开始,我先查看了本地总内存,以及各个 node 的内存残余情况。
# top
......
Mem: 65961428k total, 26748124k used, 39213304k free, 632832k buffers
Swap: 8388600k total, 0k used, 8388600k free, 1408376k cached
# cat /proc/zoneinfo"
......
Node 0, zone Normal
pages free 4651908
Node 1, zone Normal
pages free 4773314
总内存不必解释,/proc/zoneinfo
里蕴含了 node 可供应用程序申请的 free pages。node1 有 4651908 个页面,4651908\*4K=18G 的可用内存。
接下来让咱们启动 redis 实例,把其内存下限设置到超过单个 node 里的内存大小。我这里单 node 内存大小是 32G,我把 redis 设置成了 50G。开始灌入数据。最终数据全副灌完之后,
# top
Mem: 65961428k total, 53140400k used, 12821028k free, 637112k buffers
Swap: 8388600k total, 0k used, 8388600k free, 1072524k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
8356 root 20 0 62.8g 46g 1292 S 0.0 74.5 3:45.34 redis-server
# cat /proc/zoneinfo | grep "pages free"
pages free 3935
pages free 347180
pages free 1402744
pages free 1501670
试验证实,在 zone_reclaim_mode 为 1 的状况下,Redis 是均匀在两个 node 里申请节点的,并没有固定在某一个 cpu 里。
莫非是大佬们的忠告错了吗?其实不是,如果不绑定亲和性的话,分配内存是当过程在哪个 node 上的 CPU 发动内存申请,就优先在哪个 node 里分配内存。之所以是平均分配在两个 node 里,是因为 redis-server 过程试验中常常会进入被动睡眠状态,醒来后可能 CPU 就换了。所以基本上,最初看起来内存是平均分配的。如下图,CPU 进行了 500 万次的上下文切换,用 top 命令看到 cpu 也是在 node0 和 node1 跳来跳去。
# grep ctxt /proc/8356/status
voluntary_ctxt_switches: 5259503
nonvoluntary_ctxt_switches: 1449
改良办法,胜利抓获 numa 陷阱
杀死过程,内存归位
# cat /proc/zoneinfo
Node 0, zone Normal
pages free 7597369
Node 1, zone Normal
pages free 7686732
绑定 CPU 和内存的亲和性,而后再启动。
numactl --cpunodebind=0 --membind=0 /search/odin/daemon/redis/bin/redis-server /search/odin/daemon/redis/conf/redis.conf
top 命令察看到 CPU 的确始终在 node0 的节点里。node 里的内存也在疾速耗费。
# cat /proc/zoneinfo
Node 0, zone Normal
pages free 10697
Node 1, zone Normal
pages free 7686732
看,内存很快就耗费光了。咱们再看 top 命令察看到的 swap,很冲动地发现,我终于陷入到传说中的 numa 陷阱了。
Tasks: 603 total, 2 running, 601 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.7%us, 5.4%sy, 0.0%ni, 85.6%id, 8.2%wa, 0.0%hi, 0.1%si, 0.0%st
Mem: 65961428k total, 34530000k used, 31431428k free, 319156k buffers
Swap: 8388600k total, 6000792k used, 2387808k free, 777584k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
258 root 20 0 0 0 0 R 72.3 0.0 0:17.18 kswapd0
25934 root 20 0 37.5g 30g 1224 D 71.6 48.7 1:06.09 redis-server
这时候,Redis 理论应用的物理内存 RES 定格到了 30g 不再上涨,而是开始耗费 Swap。
又过了一会儿,Redis 被 oom 给 kill 了。
论断
通过明天的试验,咱们能够发现的确有 NUMA 陷阱这种货色存在。不过那是我手工通过 numactl
指令绑定 cpu 和 mem 的亲和性后才遭逢的。置信国内绝大部分的线上 Redis 没有进行这个绑定,所以实践上来单 Redis 单实例能够应用到整个机器的物理内存。(实际中最好不要这么干,你的大部分内存都绑定到一个 redis 过程里的话,那其它 CPU 核就没啥事干了,节约了 CPU 的多核计算能力)
扩大
当通过 numactl
绑定 CPU 和 mem 都在一个 node 里的时候,内存 IO 不须要通过总线,性能会比拟高,你 Redis 的 QPS 能力也会上涨。和跨 node 的内存 IO 性能比照,能够上面的实例,就是 10:21 的区别。
# numactl --hardware
......
node distances:
node 0 1
0: 10 21
1: 21 10
你要是对性能有极致的谋求,能够试着绑定 numa 的亲和性玩玩。不过,no 作 no die,掉到 numa 陷阱里可别赖我,嘎嘎!
开发内功修炼之内存篇专辑:
- 1. 带你深刻了解内存对齐最底层原理
- 2. 内存随机也比程序拜访慢,带你深刻了解内存 IO 过程
- 3. 从 DDR 到 DDR4,内存外围频率其实基本上就没太大的提高
- 4. 理论测试内存在程序 IO 和随机 IO 时的拜访延时差别
- 5. 揭穿内存厂家“谎话”,实测内存带宽实在体现
- 6.NUMA 架构下的内存拜访提早区别!
- 7.PHP7 内存性能优化的思维精华
- 8. 一次内存性能晋升的我的项目实际
- 9. 挑战 Redis 单实例内存最大极限,“遭逢”NUMA 陷阱!
我的公众号是「开发内功修炼」,在这里我不是单纯介绍技术实践,也不只介绍实践经验。而是把实践与实际联合起来,用实际加深对实践的了解、用实践进步你的技术实际能力。欢送你来关注我的公众号,也请分享给你的好友~~~