回答重點
Redis 集群(Redis cluster)是通過多個 Redis 實例組成的,每個主節點實例負責存儲部分的數據,并且可以有一個或多個從節點作為備份。
具體是采用哈希槽(Hash Slot)機制來分配數據,將整個鍵空間劃分為 16384 個槽(slots)。每個 Redis 主節點實例負責一定范圍的哈希槽,數據的 key 經過 CRC16-CCITT 哈希算法計算后對 16384 取余即可定位到對應的節點。
客戶端在發送請求時,會通過集群的任意節點進行連接,如果該節點存儲了對應的數據則直接返回,反之該節點會根據請求的鍵值計算哈希槽并路由到正確的節點。現代 Redis 客戶端通常會緩存槽位映射信息,避免頻繁重定向。
簡單來說,集群就是通過多臺機器分擔單臺機器上的壓力,同時通過主從復制提供高可用性。
擴展知識
Redis 集群中節點之間的信息如何同步?
Redis 集群內每個節點都會保存集群的完整拓撲信息,包括每個節點的 ID、IP 地址、端口、負責的哈希槽范圍等。
節點之間使用 Gossip 協議進行狀態交換,以保持集群的一致性和故障檢測。每個節點會周期性地發送 PING 和 PONG 消息,交換集群信息,使得集群信息得以同步。
Gossip 的優點:
- 最終一致性:Gossip 協議能夠在一段時間后使集群信息達成一致,雖然傳播速度不如中心化方案快,但網絡開銷更小。
- 降低網絡負擔:由于信息是以隨機節點間的對話方式傳播,避免了集中式的狀態查詢,從而降低了網絡流量。
Gossip 協議
Gossip 主要特點:
- 分布式信息傳播:每個節點定期向其他節點發送其狀態信息,確保所有節點對集群的狀態有一致的視圖。
- 低延遲和高效率:Gossip 協議設計為輕量級的通信方式,能夠快速傳播信息,減少單點故障帶來的風險。
- 去中心化:沒有中心節點,所有節點平等地參與信息傳播,提高了系統的魯棒性。
工作原理:
- 狀態報告:每個節點在特定的時間間隔內,向隨機選擇的其他節點發送其自身的狀態信息,包括節點的主從關系、槽位分布等。
- 信息更新:接收到狀態信息的節點會根據所接收到的數據更新自己的狀態,并將更新后的狀態繼續傳播給其他節點。
- 節點檢測:通過周期性交換狀態信息,節點可以檢測到其他節點的存活狀態。如果某個節點未能在預定時間內響應,則該節點會被標記為故障節點。
- 容錯處理:在檢測到節點故障后,集群中的其他節點可以采取措施(如重新分配槽位)以保持系統的高可用性。
Redis 集群分片原理圖示
Redis 集群會將數據分散到 16384(2^14)個哈希槽中,集群中的每個主節點負責一定范圍的哈希槽,在 Redis 集群中,使用 CRC16-CCITT 哈希算法計算鍵的哈希槽,以確定該鍵應存儲在哪個節點。
集群哈希槽分片如下圖所示:
每個節點會擁有一部分的槽位,然后對應的鍵值會根據其本身的 key,映射到一個哈希槽中,其主要流程如下:
- 根據鍵值的 key,按照 CRC16-CCITT 算法計算一個 16 bit 的值,然后將 16 bit 的值對 16384 進行取余運算,最后得到一個對應的哈希槽編號。
- 值得注意的是,Redis 支持哈希標簽(Hash Tags)機制,即如果鍵名中包含 “{…}” 格式的內容,那么只有花括號內的內容會被用來計算哈希槽,這樣可以確保相關聯的鍵被分配到同一個槽中。
- 根據每個節點分配的哈希槽區間,對應編號的數據落在對應的區間上,就能找到對應的分片實例。
為了方便大家理解,我這里畫一個對應的關系圖,以三個節點為例:
Redis 哈希槽映射圖
這里還有一點需要強調下,Redis 客戶端可以訪問集群中任意一臺實例,正常情況下這個實例包含這個數據
但如果槽被轉移了,客戶端還未來得及更新槽的信息,當前實例沒有這個數據,則返回 MOVED 響應給客戶端,將其重定向到對應的實例(因 Gossip 協議確保集群內每個節點都會保存集群的完整拓撲信息)。
Redis 集群中存儲 key 示例
假設我們有一個 Redis 集群,包含三個主節點(Node1、Node2、Node3),它們分別負責以下哈希槽:
- Node1: 哈希槽 0-5460
- Node2: 哈希槽 5461-10922
- Node3: 哈希槽 10923-16383
現在要存儲一個鍵為 user:1001
的數據。
計算哈希槽
- 使用 CRC16-CCITT 哈希算法計算
user:1001
的 CRC16 值。 - 假設計算結果為 12345。
- 然后,計算該值對應的哈希槽:
- 哈希槽 = 12345 % 16384 = 12345。
確定目標節點
- 12345 落在 Node3 的負責范圍(10923-16383),因此,
user:1001
會被存儲在 Node3 中。
Redis 集群中請求 key 示例(客戶端直接連接的并不是對應 key 的節點)
如果客戶端連接的是集群的 Node1,但需要訪問存儲在 Node3 的鍵 user:1001
,查詢過程如下:
查詢過程
1)計算哈希槽:
- 客戶端使用 CRC16-CCITT 算法計算
user:1001
的哈希值(假設為 12345)。 - 計算哈希槽:12345 % 16384 = 12345。
2)查詢請求:
- 因為客戶端連接的是集群中的 Node1,所以客戶端發送查詢命令
GET user:1001
到 Node1。
3)Node1 響應:
- Node1 檢測到請求的鍵
user:1001
屬于 Node3,返回一個MOVED
錯誤,指示客戶端請求的鍵在另一個節點上。MOVED
錯誤中會返回目標節點的信息(例如,Node3 的 IP 和端口)。
4)客戶端處理:
- 現代 Redis 客戶端會緩存此槽位映射信息,后續對該槽的請求會直接發送到正確的節點,避免重定向。
- 第一次收到 MOVED 錯誤時,客戶端會連接到目標節點并更新槽位映射緩存。
5)發送查詢請求到正確節點:
- 客戶端向 Node3 發送
GET user:1001
。
6)獲取結果:
- Node3 查詢到
user:1001
的值(假設為{"name": "stormsha", "age": 16}
),并返回結果。
為什么 Redis 哈希槽節點的數目是 16384 呢?
1)首先是消息大小的考慮。
正常的心跳包需要帶上節點完整配置數據,心跳還是比較頻繁的,所以需要考慮數據包的大小,如果使用 16384 數據包只要約 2KB,如果用了 65536 則需要約 8KB。
實際上槽位信息使用一個長度為 16384 位的位圖(bitmap)來表示,節點擁有哪個槽位,就將對應位置的位設置為 1,否則為 0。
Redis 心跳消息(CLUSTERMSG)數據結構如下所示:
typedef struct {char sig[4]; /* 信息標識 */uint32_t totlen; /* 消息總長度 */uint16_t ver; /* 協議版本 */uint16_t type; /* 消息類型, 用于區分meet,ping,pong等消息 */uint16_t count; /* 消息體包含的節點數量, 僅用于meet,ping,pong消息類型*/uint64_t currentEpoch; /* 當前發送節點的配置紀元 */uint64_t configEpoch; /* 主節點/從節點的主的配置紀元 */char sender[CLUSTER_NAMELEN]; /* 發送節點的nodeId */unsigned char myslots[CLUSTER_SLOTS/8]; /* 發送節點負責的槽信息 */char slaveOf[CLUSTER_NAMELEN]; /* 如果是從節點,記錄主節點的nodeId */uint16_t port; /* 端口號 */uint16_t flags; /* 發送節點標識,區分主從角色,是否下線等 */unsigned char state; /* 發送節點的集群狀態 */unsigned char mflags[3]; /* 消息標識 */union clusterMsgData data /* 消息正文 */;
} clusterMsg;
這里我們看到一個重點,即在消息頭中最占空間的是 myslots[CLUSTER_SLOTS/8]
:
- 當槽位為 65536 時,這部分大小: 65536÷8=8192 字節 ≈8KB
- 當槽位為 16384 時,這部分大小: 16384÷8=2048 字節 ≈2KB
如果槽位為 65536,這個心跳消息的頭部就太大了,在高頻率通信的集群環境中會顯著增加網絡負擔。
2)集群規模的考慮。
根據 Redis 作者的說明,集群不太可能會擴展超過 1000 個節點,而每個主節點應該管理相當數量的哈希槽。如果使用 16384 個槽位:
- 100 個節點集群:平均每個節點管理約 164 個槽位
- 1000 個節點集群:平均每個節點管理約 16 個槽位
這個數量既能滿足大多數集群規模需求,又不會因為槽位過少而影響數據分布均衡性。
3)內存占用的平衡
Redis 作為內存數據庫,對內存使用的效率非常關注。16384 個槽位的設計在集群功能和資源消耗之間取得了很好的平衡:足夠多的槽位保證良好的數據分布,同時槽位映射的內存占用和網絡傳輸開銷都保持在合理范圍內。
好的,以下是測試題目與解析部分分開的版本:
? Redis 集群選擇題(共 4 題)
1. Redis 集群采用什么機制來分配數據到不同的節點? ( )
A. 哈希槽機制
B. 主備機制
C. 哈希鏈機制
D. 分段機制
2. Redis 集群中有多少個哈希槽? ( )
A. 2048
B. 8192
C. 16384
D. 32768
3. Redis 集群中的節點之間使用什么協議進行狀態同步和故障檢測? ( )
A. TCP/IP 協議
B. HTTP 協議
C. UDP 協議
D. Gossip 協議
4. 在 Redis 集群中,當客戶端請求的鍵值對不在連接的節點上時,返回的錯誤類型是什么? ( )
A. ERROR
B. MOVED
C. NOT_FOUND
D. FORBIDDEN
📘 答案與解析
1. 正確答案:A
解析:Redis 集群通過哈希槽機制進行數據分配,避免單點瓶頸,將鍵映射到 16384 個槽中,再分配到不同節點。
2. 正確答案:C
解析:Redis 集群采用固定的 16384 個哈希槽,這個值是 21?,設計用于在帶寬與靈活性之間做出平衡。
3. 正確答案:D
解析:Redis 集群使用 Gossip 協議實現去中心化狀態同步和故障檢測,各節點定期交換信息。
4. 正確答案:B
解析:當客戶端請求的 key 不在當前節點時,Redis 返回 MOVED 重定向錯誤,引導客戶端訪問正確節點。