挑戰Redis單實例內存最大極限,「遭遇」NUMA陷阱!

圖片

咱們公司的基礎架構部有個雲Redis平臺,其中Redis實例在申請的時候能夠自由選擇須要的內存的大小。而後就引起了個人一個思考,Redis單實例內存最大申請到多大比較合適?假設母機是64GB內存的物理機,若是不考慮CPU資源的的浪費,我是否能夠開一個50G的Redis實例?
node

因而我在Google上各類搜索,討論這個問題的人彷佛很少。找到惟一感受靠譜點的答案,那就是單進程分配的內存最好不要超過一個node裏的內存總量,不然linux當該node裏的內存分配光了的時候,會在本身node裏動用硬盤swap,而不是其它node裏申請。這即便所謂的numa陷阱,當Redis進入這種狀態後會致使性能急劇降低(不僅是redis,全部的內存密集型應用如mysql,mongo等都會有相似問題)。mysql

看起來這個解釋很是有說服力。因而乎,我就想親手捕捉一次NUMA陷阱,看看這個傢伙究竟什麼樣。1先聊聊QPI與NUMA最先在CPU都是單核的時候,用的總線都是FSB總線。經典結構以下圖:
圖片圖1 單核時代的FSB總線linux

到來後來CPU的開發者們發現CPU的頻率已經接近物理極限了,無法再有更大程度的提升了。在2003年的時候,CPU的頻率就已經達到2個多GB,甚至3個G了。如今你再來看今天的CPU,基本也仍是這個頻率,沒進步多少。摩爾定律失效了,或者說是向另一個方向發展了。那就是多核化、多CPU化。redis

圖片圖2 多核時代的FSB總線sql

剛開始核很少的時候,FSB總線勉強還能夠支撐。可是隨着CPU愈來愈多,全部的數據IO都經過這一條總線和內存交換數據,這條FSB就成爲了整個計算機系統的瓶頸。舉個北京的例子,這就比如進回龍觀的京藏高速,剛開始回龍觀人口很少的時候,這條高速承載沒問題。可是如今回龍觀彙集了幾十萬人了,「總線」還僅有這一條,未免效率過低。架構

CPU的設計者們很快改變了本身的設計,引入了QPI總線,相應的CPU的結構就叫NMUA架構。下圖直觀理解app

圖片圖3 QPI總線ide

2話說NUMA陷阱性能

NUMA陷阱指的是引入QPI總線後,在計算機系統裏可能會存在的一個坑。大體的意思就是若是你的機器打開了numa,那麼你的內存即便在充足的狀況下,也會使用磁盤上的swap,致使性能低下。緣由就是NUMA爲了高效,會僅僅只從你的當前node裏分配內存,只要當前node裏用光了(即便其它node還有),也仍然會啓用硬盤swap。
spa

當我第一次據說到這個概念的時候,不由感嘆我運氣好,個人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,只會在本地節點回收內存。

3實踐捕捉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

4綁定親和性,成功捕獲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了。5結論

經過今天的實驗,咱們能夠發現確實有NUMA陷阱這種東西存在。不過那是我手工經過numactl指令綁定cpu和mem的親和性後才遭遇的。相信國內絕大部分的線上Redis沒有進行這個綁定,因此理論上來單Redis單實例可使用到整個機器的物理內存。(實踐中最好不要這麼幹,你的大部份內存都綁定到一個redis進程裏的話,那其它CPU核就沒啥事幹了,浪費了CPU的多核計算能力)

6擴展當經過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陷阱裏可別賴我,嘎嘎!

相關文章
相關標籤/搜索