我們公司的基礎架構部有個云Redis平臺,其中Redis實例在申請的時候可以自由選擇需要的內存的大小。然后就引發了我的一個思考,Redis單實例內存最大申請到多大比較合適?假設母機是64GB內存的物理機,如果不考慮CPU資源的的浪費,我是否可以開一個50G的Redis實例?
于是我在Google上各種搜索,討論這個問題的人似乎不多。找到唯一感覺靠譜點的答案,那就是單進程分配的內存最好不要超過一個node里的內存總量,否則linux當該node里的內存分配光了的時候,會在自己node里動用硬盤swap,而不是其它node里申請。這即使所謂的numa陷阱,當Redis進入這種狀態后會導致性能急劇下降(不只是redis,所有的內存密集型應用如mysql,mongo等都會有類似問題)。
看起來這個解釋非常有說服力。于是乎,我就想親手捕捉一次NUMA陷阱,看看這個家伙究竟什么樣。
1 先聊聊QPI與NUMA
最早在CPU都是單核的時候,用的總線都是FSB總線。經典結構如下圖:
圖1 單核時代的FSB總線
到來后來CPU的開發者們發現CPU的頻率已經接近物理極限了,沒法再有更大程度的提高了。在2003年的時候,CPU的頻率就已經達到2個多GB,甚至3個G了。現在你再來看今天的CPU,基本也還是這個頻率,沒進步多少。摩爾定律失效了,或者說是向另外一個方向發展了。那就是多核化、多CPU化。
圖2 多核時代的FSB總線
剛開始核不多的時候,FSB總線勉強還可以支撐。但是隨著CPU越來越多,所有的數據IO都通過這一條總線和內存交換數據,這條FSB就成為了整個計算機系統的瓶頸。舉個北京的例子,這就好比進回龍觀的京藏高速,剛開始回龍觀人口不多的時候,這條高速承載沒問題。但是現在回龍觀聚集了幾十萬人了,“總線”還僅有這一條,未免效率太低。
CPU的設計者們很快改變了自己的設計,引入了QPI總線,相應的CPU的結構就叫NMUA架構。下圖直觀理解
圖3 QPI總線
2 話說NUMA陷阱
NUMA陷阱指的是引入QPI總線后,在計算機系統里可能會存在的一個坑。大致的意思就是如果你的機器打開了numa,那么你的內存即使在充足的情況下,也會使用磁盤上的swap,導致性能低下。原因就是NUMA為了高效,會僅僅只從你的當前node里分配內存,只要當前node里用光了(即使其它node還有),也仍然會啟用硬盤swap。
當我第一次聽說到這個概念的時候,不禁感嘆我運氣好,我的Redis實例貌似從來沒有掉進這個陷阱里過。那為了以后也別栽坑,趕緊去了解了下我的機器的numa狀態:
上面結果說明我們有兩個node,node0和node1,分別有12個核心,各有32GB的內存。再看zone_reclaim_mode,它用來管理當一個內存區域(zone)內部的內存耗盡時,是從其內部進行內存回收還是可以從其他zone進行回收的選項:
0 關閉zone_reclaim模式,可以從其他zone或NUMA節點回收內存
1 打開zone_reclaim模式,這樣內存回收只會發生在本地節點內
2在本地回收內存時,可以將cache中的臟數據寫回硬盤,以回收內存
4 在本地回收內存時,表示可以用Swap 方式回收內存
3 實踐捕捉numa陷阱未遂
那我的好奇心就來了,既然我的單個node節點只有32G,那我部署一個50G的Redis,給它填滿數據試試到底會不會發生swap。
實驗開始,我先查看了本地總內存,以及各個node的內存剩余狀況。
總內存不用解釋,/proc/zoneinfo里包含了node可供應用程序申請的free pages。node1有4651908個頁面,4651908*4K=18G的可用內存。接下來讓我們啟動redis實例,把其內存上限設置到超過單個node里的內存大小。我這里單node內存大小是32G,我把redis設置成了50G。開始灌入數據。最終數據全部灌完之后,
實驗證明,在zone_reclaim_mode為1的情況下,Redis是平均在兩個node里申請節點的,并沒有固定在某一個cpu里。
莫非是大佬們的忠告錯了嗎?其實不是,如果不綁定親和性的話,分配內存是當進程在哪個node上的CPU發起內存申請,就優先在哪個node里分配內存。之所以是平均分配在兩個node里,是因為redis-server進程實驗中經常會進入主動睡眠狀態,醒來后可能CPU就換了。所以基本上,最后看起來內存是平均分配的。如下圖,CPU進行了500萬次的上下文切換,用top命令看到cpu也是在node0和node1跳來跳去。
4 綁定親和性,成功捕獲NUMA陷阱
殺死進程,內存歸位
綁定CPU和內存的親和性,然后再啟動。
numactl --cpunodebind=0 --membind=0 /search/odin/daemon/redis/bin/redis-server /search/odin/daemon/redis/conf/redis.conf top命令觀察到CPU確實一直在node0的節點里。node里的內存也在快速消耗。
看,內存很快就消耗光了。我們再看top命令觀察到的swap,很激動地發現,我終于陷入到傳說中的numa陷阱了。
這時候,Redis實際使用的物理內存RES定格到了30g不再上漲,而是開始消耗Swap。又過了一會兒,Redis被oom給kill了。
5 結論
通過今天的實驗,我們可以發現確實有NUMA陷阱這種東西存在。不過那是我手工通過numactl指令綁定cpu和mem的親和性后才遭遇的。相信國內絕大部分的線上Redis沒有進行這個綁定,所以理論上來單Redis單實例可以使用到整個機器的物理內存。(實踐中最好不要這么干,你的大部分內存都綁定到一個redis進程里的話,那其它CPU核就沒啥事干了,浪費了CPU的多核計算能力)
6 擴展
當通過numactl綁定CPU和mem都在一個node里的時候,內存IO不需要經過總線,性能會比較高,你Redis的QPS能力也會上漲。和跨node的內存IO性能對比,可以下面的實例,就是10:21的區別。
你要是對性能有極致的追求,可以試著綁定numa的親和性玩玩。不過,no作no die,掉到numa陷阱里可別賴我,嘎嘎!
審核編輯:劉清
-
Linux系統
+關注
關注
4文章
594瀏覽量
27410 -
總線
+關注
關注
10文章
2888瀏覽量
88134 -
FSB
+關注
關注
0文章
7瀏覽量
9411 -
Redis
+關注
關注
0文章
376瀏覽量
10882
發布評論請先 登錄
相關推薦
評論