我們公司的基礎(chǔ)架構(gòu)部有個云Redis平臺舞箍,其中Redis實例在申請的時候可以自由選擇需要的內(nèi)存的大小。然后就引發(fā)了我的一個思考皆疹,Redis單實例內(nèi)存最大申請到多大比較合適疏橄?假設(shè)母機是64GB內(nèi)存的物理機,如果不考慮CPU資源的的浪費,我是否可以開一個50G的Redis實例捎迫?
于是我在Google上各種搜索晃酒,討論這個問題的人似乎不多。找到唯一感覺靠譜點的答案窄绒,那就是單進程分配的內(nèi)存最好不要超過一個node里的內(nèi)存總量贝次,否則linux當該node里的內(nèi)存分配光了的時候,會在自己node里動用硬盤swap彰导,而不是其它node里申請蛔翅。這即使所謂的numa陷阱,當Redis進入這種狀態(tài)后會導致性能急劇下降(不只是redis位谋,所有的內(nèi)存密集型應(yīng)用如mysql山析,mongo等都會有類似問題)。
看起來這個解釋非常有說服力倔幼。于是乎盖腿,我就想親手捕捉一次NUMA陷阱,看看這個家伙究竟什么樣损同。
先聊聊QPI與NUMA
最早在CPU都是單核的時候翩腐,用的總線都是FSB總線。經(jīng)典結(jié)構(gòu)如下圖:
到來后來CPU的開發(fā)者們發(fā)現(xiàn)CPU的頻率已經(jīng)接近物理極限了膏燃,沒法再有更大程度的提高了茂卦。在2003年的時候,CPU的頻率就已經(jīng)達到2個多GB组哩,甚至3個G了〉攘現(xiàn)在你再來看今天的CPU,基本也還是這個頻率伶贰,沒進步多少蛛砰。摩爾定律失效了,或者說是向另外一個方向發(fā)展了黍衙。那就是多核化泥畅、多CPU化。
剛開始核不多的時候琅翻,F(xiàn)SB總線勉強還可以支撐位仁。但是隨著CPU越來越多,所有的數(shù)據(jù)IO都通過這一條總線和內(nèi)存叫喚數(shù)據(jù)方椎,這條FSB就成為了整個計算機系統(tǒng)的瓶頸聂抢。舉個北京的例子,這就好比進回龍觀的京藏高速棠众,剛開始回龍觀人口不多的時候琳疏,這條高速承載沒問題。但是現(xiàn)在回龍觀聚集了幾十萬人了,“總線”還僅有這一條空盼,未免效率太低疮薇。
CPU的設(shè)計者們很快改變了自己的設(shè)計,引入了QPI總線我注,相應(yīng)的CPU的結(jié)構(gòu)就叫NMUA架構(gòu)。下圖直觀理解
話說NUMA陷阱
NUMA陷阱指的是引入QPI總線后迟隅,在計算機系統(tǒng)里可能會存在的一個坑但骨。大致的意思就是如果你的機器打開了numa,那么你的內(nèi)存即使在充足的情況下智袭,也會使用磁盤上的swap奔缠,導致性能低下。原因就是NUMA為了高效吼野,會僅僅只從你的當前node里分配內(nèi)存校哎,只要當前node里用光了(即使其它node還有),也仍然會啟用硬盤swap瞳步。
當我第一次聽說到這個概念的時候闷哆,不禁感嘆我運氣好,我的Redis實例貌似從來沒有掉進這個陷阱里過单起。那為了以后也別栽坑抱怔,趕緊去了解了下我的機器的numa狀態(tài):
# 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
上面結(jié)果說明我們有兩個node,node0和node1嘀倒,分別有12個核心屈留,各有32GB的內(nèi)存。
再看zone_reclaim_mode测蘑,它用來管理當一個內(nèi)存區(qū)域(zone)內(nèi)部的內(nèi)存耗盡時灌危,是從其內(nèi)部進行內(nèi)存回收還是可以從其他zone進行回收的選項:
- 0 關(guān)閉zone_reclaim模式,可以從其他zone或NUMA節(jié)點回收內(nèi)存
- 1 打開zone_reclaim模式碳胳,這樣內(nèi)存回收只會發(fā)生在本地節(jié)點內(nèi)
- 2 在本地回收內(nèi)存時勇蝙,可以將cache中的臟數(shù)據(jù)寫回硬盤,以回收內(nèi)存
- 4 在本地回收內(nèi)存時固逗,表示可以用Swap 方式回收內(nèi)存
# cat /proc/sys/vm/zone_reclaim_mode
1
額浅蚪,好吧。我的這臺機器上的zone_reclaim_mode還真是1烫罩,只會在本地節(jié)點回收內(nèi)存惜傲。
實踐捕捉numa陷阱未遂
那我的好奇心就來了,既然我的單個node節(jié)點只有32G贝攒,那我部署一個50G的Redis盗誊,給它填滿數(shù)據(jù)試試到底會不會發(fā)生swap。
實驗開始,我先查看了本地總內(nèi)存哈踱,以及各個node的內(nèi)存剩余狀況荒适。
# 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
總內(nèi)存不用解釋,/proc/zoneinfo
里包含了node可供應(yīng)用程序申請的free pages开镣。node1有4651908個頁面刀诬,4651908*4K=18G的可用內(nèi)存。
接下來讓我們啟動redis實例邪财,把其內(nèi)存上限設(shè)置到超過單個node里的內(nèi)存大小陕壹。我這里單node內(nèi)存大小是32G,我把redis設(shè)置成了50G树埠。開始灌入數(shù)據(jù)糠馆。最終數(shù)據(jù)全部灌完之后,
# 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里申請節(jié)點的,并沒有固定在某一個cpu里绊袋。
莫非是大佬們的忠告錯了嗎毕匀?其實不是,如果不綁定親和性的話癌别,分配內(nèi)存是當進程在哪個node上的CPU發(fā)起內(nèi)存申請期揪,就優(yōu)先在哪個node里分配內(nèi)存。之所以是平均分配在兩個node里规个,是因為redis-server進程實驗中經(jīng)常會進入主動睡眠狀態(tài)凤薛,醒來后可能CPU就換了。所以基本上诞仓,最后看起來內(nèi)存是平均分配的缤苫。如下圖,CPU進行了500萬次的上下文切換墅拭,用top命令看到cpu也是在node0和node1跳來跳去活玲。
# grep ctxt /proc/8356/status
voluntary_ctxt_switches: 5259503
nonvoluntary_ctxt_switches: 1449
改進方法,成功抓獲numa陷阱
殺死進程谍婉,內(nèi)存歸位
# cat /proc/zoneinfo
Node 0, zone Normal
pages free 7597369
Node 1, zone Normal
pages free 7686732
綁定CPU和內(nèi)存的親和性舒憾,然后再啟動。
numactl --cpunodebind=0 --membind=0 /search/odin/daemon/redis/bin/redis-server /search/odin/daemon/redis/conf/redis.conf
top命令觀察到CPU確實一直在node0的節(jié)點里穗熬。node里的內(nèi)存也在快速消耗镀迂。
# cat /proc/zoneinfo
Node 0, zone Normal
pages free 10697
Node 1, zone Normal
pages free 7686732
看,內(nèi)存很快就消耗光了唤蔗。我們再看top命令觀察到的swap探遵,很激動地發(fā)現(xiàn)窟赏,我終于陷入到傳說中的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實際使用的物理內(nèi)存RES定格到了30g不再上漲涯穷,而是開始消耗Swap。
又過了一會兒藏雏,Redis被oom給kill了拷况。
結(jié)論
通過今天的實驗,我們可以發(fā)現(xiàn)確實有NUMA陷阱這種東西存在掘殴。不過那是我手工通過numactl
指令綁定cpu和mem的親和性后才遭遇的蝠嘉。相信國內(nèi)絕大部分的線上Redis沒有進行這個綁定,所以理論上來單Redis單實例可以使用到整個機器的物理內(nèi)存杯巨。(實踐中最好不要這么干,你的大部分內(nèi)存都綁定到一個redis進程里的話努酸,那其它CPU核就沒啥事干了服爷,浪費了CPU的多核計算能力)
擴展
當通過numactl
綁定CPU和mem都在一個node里的時候,內(nèi)存IO不需要經(jīng)過總線获诈,性能會比較高仍源,你Redis的QPS能力也會上漲。和跨node的內(nèi)存IO性能對比舔涎,可以下面的實例笼踩,就是10:21的區(qū)別。
# numactl --hardware
......
node distances:
node 0 1
0: 10 21
1: 21 10
你要是對性能有極致的追求亡嫌,可以試著綁定numa的親和性玩玩嚎于。不過,no作no die挟冠,掉到numa陷阱里可別賴我于购,嘎嘎!
歡迎搜索微信公眾號:開發(fā)內(nèi)功修煉