当前位置: 首页 > Linux

挑战Redis单实例内存最大极限,“遭遇”NUMA陷阱!

时间:2023-04-06 22:05:56 Linux

我们公司的基础设施部有一个云Redis平台,Redis实例在申请的时候可以自由选择需要的内存大小。然后引发了我的思考,Redis单实例的最大内存申请是多少合适?假设master机器是64GB内存的物理机,如果不考虑CPU资源的浪费,是否可以开一个50G的Redis实例呢?于是google了一下,好像讨论这个问题的人不多。唯一感觉靠谱的答案是,单个进程分配的内存不要超过一个节点内存总量,否则linux会在节点内存全部分配完后,使用自己节点的硬盘swap,而是在其他节点中应用。这就是所谓的numatrap,当Redis进入这种状态时,会导致性能急剧下降(不仅仅是redis,所有内存密集型应用如mysql、mongo等都会有类似的问题)。这个解释似乎很有说服力。于是乎,就想亲手抓个NUMA陷阱,看看这家伙长什么样子。先说QPI和NUMA。当CPU都是单核时,使用的总线是FSB总线。经典结构如下图所示:CPU的开发人员到货后发现,CPU的频率已经接近物理极限,没有办法更大程度的提升。2003年的时候,CPU的频率已经达到2GB以上,甚至3G。现在你看今天的CPU,基本上还是这个频率,并没有太大的提升。摩尔定律失效了,或者说它向另一个方向发展了。那就是多核,多CPU。当初核心不多的时候,外频总线勉强支撑。但是随着CPU越来越多,所有的数据IO都是通过这个总线和内存来调用数据的,这个外频就成了整个计算机系统的瓶颈。举个北京的例子,这好比京藏高速进入回龙观。当初回龙观人口不多的时候,这条高速公路的承载能力是没有问题的。可现在回龙观聚集了几十万人,就只有这辆“公交车”,效率太低了。CPU设计者很快改变了他们的设计并引入了QPI总线。对应的CPU结构称为NMUA架构。下图直观理解NUMA陷阱NUMA陷阱指的是引入QPI总线后计算机系统可能存在的一个坑。大致的意思就是如果你的机器开启了numa,即使你的内存足够,也会使用磁盘上的swap,导致性能低下。原因是NUMA只会从您当前的节点分配内存以提高效率。只要当前节点用完(即使还有其他节点),硬盘交换仍然会启用。当我第一次听说这个概念时,不禁感叹自己很幸运,我的Redis实例似乎从未落入这个陷阱。为了以后不卡,赶紧去了解一下自己机器的numa状态:#numactl--hardwareavailable:2nodes(0-1)node0cpus:012345121314151617node0大小:32756MBnode0免费:19642MBnode1cpus:67891011181920212223node1大小:32768MBnode1免费:18652MBnode距离:node010:10211:2110我们有两个节点,node0和node1,每个节点有12个内核和32GB内存。再看zone_reclaim_mode,它是用来管理当一个内存区域(zone)里面的内存耗尽时,是从里面回收内存还是从其他zone回收内存:0关闭zone_reclaimmode,可以从其他zone或者NUMA使用节点回收内存1开启zone_reclaim模式,让内存回收只发生在本地节点2本地回收内存时,可以将缓存中的脏数据写回硬盘回收内存4本地回收内存时,你可以使用Swap的方式来回收内存#cat/proc/sys/vm/zone_reclaim_mode1额,好吧。我机器上的zone_reclaim_mode真的是1,本地节点只会回收内存。我试图在实践中抓住numa陷阱,所以我的好奇心来了。由于我的单node节点只有32G,我就部署一个50G的Redis,往里面填数据看看会不会swap。在实验开始的时候,我首先查看了本地总内存和每个节点的剩余内存状态。#top……内存:总计65961428k,已用26748124k,空闲39213304k,缓冲区632832k交换:总计8388600k,已用0k,空闲8388600k,缓存1408376k#cat/proc/zoneinfo"zoneinfo"...节点0,正常页面空闲4651908Node1、zoneNormalpagesfree4773314总内存不用解释。/proc/zoneinfo包含节点可以申请的空闲页面。Node1有4651908页,可用内存4651908\*4K=18G。那么接下来,我们启动redis实例,将其内存上限设置为超过单个节点的内存大小。这里单节点内存大小为32G,我设置redis为50G。开始填写数据。最后,所有数据都填满后,#topMem:65961428ktotal,53140400kused,12821028kfree,637112kbuffersSwap:8388600ktotal,0kused,8388600kfree,1072524kcachedPIDUSERPRNIVIRTRESDSHRS%CPU%MEM3TIME5COMMAN6root20062.8g46g1292S0.074.53:45.34redis服务器#cat/proc/zoneinfo|grep"pagesfree"pagesfree3935pagesfree347180pagesfree1402744pagesfree1501670实验证明,当zone_reclaim_mode为1时,Redis平均在两个节点申请节点的并不固定在某个cpu。难道大佬们的建议是错误的?事实上,它不是。如果不绑定affinity,那么分配内存就是当进程在哪个节点上的CPU发起内存申请时,先在哪个节点分配内存。之所以均匀分布在两个节点,是因为redis-server进程实验经常进入activesleep状态,唤醒后可能会换CPU。所以基本上,最后看起来内存是均匀分配的。如下图,CPU进行了500万次上下文切换,top命令显示CPU也在node0和node1之间跳转。#grepctxt/proc/8356/statusvoluntary_ctxt_switches:5259503nonvoluntary_ctxt_switches:1449改进的方法,成功捕捉到numa陷阱杀死进程,内存归巢#cat/proc/zoneinfoNode0,zoneNormalpagesfree7597369Node1,bindzoneNormal3pagesfreCPU和开始前的记忆亲和力。numactl--cpunodebind=0--membind=0/search/odin/daemon/redis/bin/redis-server/search/odin/daemon/redis/conf/redis.conftop命令观察到CPU确实一直在node0里面的节点。节点中的内存也在快速消耗。#cat/proc/zoneinfoNode0,zoneNormalpagesfree10697Node1,zoneNormalpagesfree7686732看,内存很快就用完了。再看看top命令观察到的swap,激动的发现终于掉进了传说中的numa陷阱。任务:总共603个,运行2个,睡眠601个,停止0个,僵尸0个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%STMEM:65961428K总数,34530000K使用,31431428K免费,319156K缓冲区:8388600K总数,6000792K使用,2387808K免费,免费,777584K缓存的piDprnivirtprnivirtresshrssShrs%cpu%cpu%cpu%cpu%cpu%cpu%cpu%cpu;:17.18kswapd025934root20037.5g30g1224D71.648.71:06.09redis-server此时Redis实际使用的物理内存RES冻结为30g不再增加,而是开始消耗Swap。一段时间后,Redis被oom杀死了。结语通过今天的实验,我们可以发现确实存在NUMA陷阱这种东西。但是那是我通过numactl命令手动绑定cpu和mem的affinity后才遇到的。相信国内大部分线上的Redis都没有这个绑定,所以理论上单个Redis实例可以使用整机的物理内存。(实际中最好不要这样做,如果你的大部分内存都绑定到一个redis进程上,那么其他CPU核心就无事可做了,CPU的多核计算能力就白白浪费了。)扩展,通过numactl绑定CPU,当mem和mem在同一个节点时,内存IO不需要走总线,性能会更高,同时你的Redis的QPS能力也会提高。比较跨节点的内存IO性能,下面的例子显示了10:21的差异。#numactl--hardware......nodedistances:node010:10211:2110如果对性能有极致追求,可以尝试玩玩numa的亲和力。不过,不死不休,别怪我落入沼泽陷阱,嘎嘎!练内功:内存部分:1.带你了解内存对齐的底层原理2.内存随机访问比顺序访问慢,让你深刻理解内存IO流程3.从DDR到DDR4,内存核心频率基本一致没太大进步4.实测内存有顺序IO和随机IO的访问延迟差异5.揭穿内存厂商的“谎言”,实测内存带宽的实际表现6.NUMA架构下内存访问延迟的区别!7.PHP7内存性能优化精髓8.一个内存性能提升的项目实践9.挑战Redis单实例内存最大极限,“遭遇”NUMA陷阱!我的公众号是“练内功练功”。在这里,我不是简单地介绍技术理论,也不是只介绍实践经验。而是理论联系实际,用实践加深对理论的理解,用理论提高技术实践能力。欢迎关注我的公众号,分享给你的朋友吧~~~