我們公司的基礎架構部有個云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陷阱!
我的公眾號是「開發內功修煉」,在這里我不是單純介紹技術理論,也不只介紹實踐經驗,而是把理論與實踐結合起來,用實踐加深對理論的理解、用理論提高你的技術實踐能力,歡迎你來關注我的公眾號,也請分享給你的好友~~~
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/200874.html
標籤:其他
下一篇:030_Java方法
