1. 程式人生 > >【原創】為什麼Redis叢集有16384個槽

【原創】為什麼Redis叢集有16384個槽

引言

我在《那些年用過的Redis叢集架構(含面試解析)》一文裡提到過,現在redis叢集架構,redis cluster用的會比較多。
如下圖所示

對於客戶端請求的key,根據公式HASH_SLOT=CRC16(key) mod 16384,計算出對映到哪個分片上,然後Redis會去相應的節點進行操作!

那大家思考過,為什麼有16384個槽麼?
ps:CRC16演算法產生的hash值有16bit,該演算法可以產生2^16-=65536個值。換句話說,值是分佈在0~65535之間。那作者在做mod運算的時候,為什麼不mod65536,而選擇mod16384?

其實我當初第一次思考這個問題的時候,我心裡是這麼想的,作者應該是覺得16384就夠了,然後我就開始查這方面資料。

很幸運的是,這個問題,作者是給出了回答的!
地址如下:
https://github.com/antirez/redis/issues/2576

作者原版回答如下:
The reason is:

  • Normal heartbeat packets carry the full configuration of a node, that can be replaced in an idempotent way with the old in order to update an old config. This means they contain the slots configuration for a node, in raw form, that uses 2k of space with16k slots, but would use a prohibitive 8k of space using 65k slots.
  • At the same time it is unlikely that Redis Cluster would scale to more than 1000 mater nodes because of other design tradeoffs.

So 16k was in the right range to ensure enough slots per master with a max of 1000 maters, but a small enough number to propagate the slot configuration as a raw bitmap easily. Note that in small clusters the bitmap would be hard to compress because when N is small the bitmap would have slots/N bits set that is a large percentage of bits set.

因此,能看懂上面那段話的讀者。這篇文章不用看了,因為作者講的很清楚了。本文只是對上面那段話做一些解釋而已。

正文

基礎

我們回憶一下Redis Cluster的工作原理!
這裡要先將節點握手講清楚。我們讓兩個redis節點之間進行通訊的時候,需要在客戶端執行下面一個命令

127.0.0.1:7000>cluster meet 127.0.0.1:7001

如下圖所示

意思很簡單,讓7000節點和7001節點知道彼此存在!
在握手成功後,連個節點之間會定期傳送ping/pong訊息,交換資料資訊,如下圖所示。

在這裡,我們需要關注三個重點。

  • (1)交換什麼資料資訊
  • (2)資料資訊究竟多大
  • (3)定期的頻率什麼樣

到底在交換什麼資料資訊?
交換的資料資訊,由訊息體和訊息頭組成。
訊息體無外乎是一些節點標識啊,IP啊,埠號啊,傳送時間啊。這與本文關係不是太大,我不細說。
我們來看訊息頭,結構如下

注意看紅框的內容,type表示訊息型別。
另外,訊息頭裡面有個myslots的char陣列,長度為16383/8,這其實是一個bitmap,每一個位代表一個槽,如果該位為1,表示這個槽是屬於這個節點的。

到底資料資訊究竟多大?
在訊息頭中,最佔空間的是myslots[CLUSTER_SLOTS/8]。這塊的大小是:
16384÷8÷1024=2kb
那在訊息體中,會攜帶一定數量的其他節點資訊用於交換。
那這個其他節點的資訊,到底是幾個節點的資訊呢?
約為叢集總節點數量的1/10,至少攜帶3個節點的資訊。
這裡的重點是:節點數量越多,訊息體內容越大。

訊息體大小是10個節點的狀態資訊約1kb。

那定期的頻率是什麼樣的?
redis叢集內節點,每秒都在發ping訊息。規律如下

  • (1)每秒會隨機選取5個節點,找出最久沒有通訊的節點發送ping訊息
  • (2)每100毫秒(1秒10次)都會掃描本地節點列表,如果發現節點最近一次接受pong訊息的時間大於cluster-node-timeout/2 則立刻傳送ping訊息

因此,每秒單節點發出ping訊息數量為
數量=1+10*num(node.pong_received>cluster_node_timeout/2)

那大致頻寬損耗如下所示,圖片來自《Redis運維與實現》

講完基礎知識以後,我們可以來看作者的回答了。

回答

(1)如果槽位為65536,傳送心跳資訊的訊息頭達8k,傳送的心跳包過於龐大。
如上所述,在訊息頭中,最佔空間的是myslots[CLUSTER_SLOTS/8]
當槽位為65536時,這塊的大小是:
65536÷8÷1024=8kb
因為每秒鐘,redis節點需要傳送一定數量的ping訊息作為心跳包,如果槽位為65536,這個ping訊息的訊息頭太大了,浪費頻寬。
(2)redis的叢集主節點數量基本不可能超過1000個。
如上所述,叢集節點越多,心跳包的訊息體內攜帶的資料越多。如果節點過1000個,也會導致網路擁堵。因此redis作者,不建議redis cluster節點數量超過1000個。
那麼,對於節點數在1000以內的redis cluster叢集,16384個槽位夠用了。沒有必要拓展到65536個。
(3)槽位越小,節點少的情況下,壓縮比高
Redis主節點的配置資訊中,它所負責的雜湊槽是通過一張bitmap的形式來儲存的,在傳輸過程中,會對bitmap進行壓縮,但是如果bitmap的填充率slots / N很高的話(N表示節點數),bitmap的壓縮率就很低。
如果節點數很少,而雜湊槽數量很多的話,bitmap的壓縮率就很低。

ps:檔案壓縮率指的是,檔案壓縮前後的大小比。

綜上所述,作者決定取16384個槽,不多不少,剛剛好!

總結

希望大家有所收穫