Redis 三種集群模式,你還傻傻分不清嗎?
前言
Redis 作為一種高性能的內(nèi)存數(shù)據(jù)庫,普遍用于目前主流的分布式架構系統(tǒng)中。為了提高系統(tǒng)的容錯率,使用多實例的 Redis 也是必不可免的,但同樣復雜度也相比單實例高出很多。本文主要會介紹 Redis 在多機數(shù)據(jù)庫下的三種實現(xiàn)。
主從模式
Redis 的主從模式指的就是主從復制。
用戶可以通過 SLAVEOF 命令或者配置的方式,讓一個服務器去復制另一個服務器即成為它的從服務器。
主從模式架構
Redis 如何實現(xiàn)主從模式?
Redis 的從服務器在向主服務器發(fā)起同步時,一般會使用 SYNC 或 PSYNC 命令。
初次同步
當從服務器收到 SLAVEOF 命令后,會向其主服務器執(zhí)行同步操作,進入主從復制流程。
- 從服務器向主服務器發(fā)起SYNC 或 PSYNC 命令
- 主服務器執(zhí)行 BGSAVE命令,生成 RDB 文件,并使用緩存區(qū)記錄從現(xiàn)在開始的所有寫命令
- RDB 文件生成完成后,主服務器會將其發(fā)送給從服務器
- 從服務器載入 RDB 文件,將自己的數(shù)據(jù)庫狀態(tài)同步更新為主服務器執(zhí)行 BGSAVE命令時的狀態(tài)。
- 主服務器將緩沖區(qū)的所有寫命令發(fā)送給從服務器,從服務將執(zhí)行這些寫命令,數(shù)據(jù)庫狀態(tài)同步為主服務器最新狀態(tài)。
SYNC 與 PSYNC 的區(qū)別
當主從同步完成后,如果此時從服務器宕機了一段時間,重新上線后勢必要重新同步一下主服務器,SYNC與 PSYNC命令的區(qū)別就在于斷線后重復制階段處理的方式不同。
- SYNC
從服務器重新向主服務器發(fā)起 SYNC命令,主服務器將所有數(shù)據(jù)再次重新生成 RDB 快照發(fā)給從服務器開始同步
- PSYNC
從服務器重新向主服務器發(fā)起 PSYNC命令。主服務器根據(jù)雙方數(shù)據(jù)的偏差量判斷是否是需要完整重同步還是僅將斷線期間執(zhí)行過的寫命令發(fā)給從服務器。
明顯可以發(fā)先 PSYNC 相比 SYNC 效率好很多,要知道同步所有數(shù)據(jù)是一個非常費資源(磁盤 IO,網(wǎng)絡)的操作,而如果只是因為短暫網(wǎng)絡不穩(wěn)定就同步所有資源是非常不值的。因此 Redis 在 2.8 版本后都開始使用 PSYNC 進行復制
PSYNC 如何實現(xiàn)部分重同步?
實現(xiàn)部分重同步主要靠三部分
1. 記錄復制偏移量
主服務器與從服務器都會維護一個復制偏移量。
- 當主服務器向從服務器發(fā)送 N 個字節(jié)的數(shù)據(jù)后,會將自己的復制偏移量 +N。
- 當從服務器收到主服務器 N 個字節(jié)大小數(shù)據(jù)后,也會將自己的復制偏移量 +N。
當主從雙方數(shù)據(jù)是同步時,這個偏移量是相等的。而一旦有個從服務器斷線一段時間而少收到了部分數(shù)據(jù)。那么此時主從雙方的服務器偏移量是不相等的,而他們的差值就是少傳輸?shù)淖止?jié)數(shù)量。如果少傳輸?shù)臄?shù)據(jù)量不是很大,沒有超過主服務器的復制積壓緩沖區(qū)大小,那么將會直接將緩沖區(qū)內(nèi)容發(fā)送給從服務器避免完全重同步。反之還是需要完全重同步的。
2. 復制積壓緩沖區(qū)
復制積壓緩沖區(qū)是由主服務器維護的一個先進先出的字節(jié)隊列,默認大小是 1mb。每當向從服務器發(fā)送寫命令時,都會將這些數(shù)據(jù)存入這個隊列。每個字節(jié)都會記錄自己的復制偏移量。從服務器在重連時會將自己的復制偏移量發(fā)送給主服務器,如果該復制偏移量之后的數(shù)據(jù)存在于復制積壓緩沖區(qū)中,則僅需要將之后的數(shù)據(jù)發(fā)送給從服務器即可。
3. 記錄服務器 ID
當執(zhí)行主從同步時,主服務器會將自己的服務器 ID (一般是自動生成的 UUID ) 發(fā)送給從服務器。從服務器在斷線恢復后會判斷該 ID 是否為當前連接的主服務器。如果是同一個 ID 則代表主服務器沒變嘗試部分重同步。如果不是同一個 ID 代表主服務有變動,則會與主服務器完全重同步。
具體流程圖如下:
Redis 哨兵模式 (Sentinel)
Redis 主從模式雖然能做到很好的數(shù)據(jù)備份,但是他并不是高可用的。一旦主服務器點宕機后,只能通過人工去切換主服務器。因此 Redis 的哨兵模式也就是為了解決主從模式的高可用方案。
哨兵模式引入了一個 Sentinel 系統(tǒng)去監(jiān)視主服務器及其所屬的所有從服務器。一旦發(fā)現(xiàn)有主服務器宕機后,會自動選舉其中的一個從服務器升級為新主服務器以達到故障轉義的目的。
同樣的 Sentinel 系統(tǒng)也需要達到高可用,所以一般也是集群,互相之間也會監(jiān)控。而 Sentinel 其實本身也是一個以特殊模式允許 Redis 服務器。
實現(xiàn)原理
1.Sentinel 與主從服務器建立連接
- Sentinel 服務器啟動之后便會創(chuàng)建于主服務器的 命令連接 ,并訂閱主服務器的 sentinel:hello 頻道以創(chuàng)建 訂閱連接
- Sentinel 默認會每 10 秒向主服務器發(fā)送 INFO 命令,主服務器則會返回主服務器本身的信息,以及其所有從服務器的信息。
- 根據(jù)返回的信息,Sentinel 服務器如果發(fā)現(xiàn)有新的從服務器上線后也會像連接主服務器時一樣,向從服務器同時創(chuàng)建命令連接與訂閱連接。
2.判定主服務器是否下線
每一個 Sentinel 服務器每秒會向其連接的所有實例包括主服務器,從服務器,其他 Sentinel 服務器)發(fā)送 PING命令,根據(jù)是否回復 PONG 命令來判斷實例是否下線。
判定主觀下線
如果實例在收到 PING命令的 down-after-milliseconds 毫秒內(nèi)(根據(jù)配置),未有有效回復。則該實例將會被發(fā)起 PING命令的 Sentinel 認定為主觀下線。
判定客觀下線
當一臺主服務器被某個 Sentinel 服務器判定為客觀下線時,為了確保該主服務器是真的下線, Sentinel 會向 Sentinel 集群中的其他的服務器確認,如果判定主服務器下線的 Sentinel 服務器達到一定數(shù)量時(一般是 N/2+1),那么該主服務器將會被判定為客觀下線,需要進行故障轉移。
3.選舉領頭 Sentinel
當有主服務器被判定客觀下線后,Sentinel 集群會選舉出一個領頭 Sentinel 服務器來對下線的主服務器進行故障轉移操作。整個選舉其實是基于 RAFT 一致性算法而實現(xiàn)的,大致的思路如下:
- 每個發(fā)現(xiàn)主服務器下線的 Sentinel 都會要求其他 Sentinel 將自己設置為局部領頭 Sentinel。
- 接收到的 Sentinel 可以同意或者拒絕
- 如果有一個 Sentinel 得到了半數(shù)以上 Sentinel 的支持則在此次選舉中成為領頭 Sentinel。
- 如果給定時間內(nèi)沒有選舉出領頭 Sentinel,那么會再一段時間后重新開始選舉,直到選舉出領頭 Sentinel。
4.選舉新的主服務器
領頭服務器會從從服務中挑選出一個最合適的作為新的主服務器。挑選的規(guī)則是:
- 選擇健康狀態(tài)的從節(jié)點,排除掉斷線的,最近沒有回復過 INFO命令的從服務器。
- 選擇優(yōu)先級配置高的從服務器
- 選擇復制偏移量大的服務器(表示數(shù)據(jù)最全)
挑選出新的主服務器后,領頭服務器將會向新主服務器發(fā)送 SLAVEOF no one命令將他真正升級為主服務器,并且修改其他從服務器的復制目標,將舊的主服務器設為從服務器,以此來達到故障轉移。
Redis Cluster
Redis 哨兵模式實現(xiàn)了高可用,讀寫分離,但是其主節(jié)點仍然只有一個,即寫入操作都是在主節(jié)點中,這也成為了性能的瓶頸。
因此 Redis 在 3.0 后加入了 Cluster 模式,它采用去無心節(jié)點方式實現(xiàn),集群將會通過分片方式保存數(shù)據(jù)庫中的鍵值對
節(jié)點
一個 Redis 集群中會由多個節(jié)點組成,每個節(jié)點都是互相連接的,會保存自己與其他節(jié)點的信息。節(jié)點之間通過 gossip 協(xié)議交換互相的狀態(tài),以及保新加入的節(jié)點信息。
數(shù)據(jù)的 Sharding
Redis Cluster 的整個數(shù)據(jù)庫將會被分為 16384 個哈希槽,數(shù)據(jù)庫中的每個鍵都屬于這 16384 個槽中的其中一個,集群中的每個節(jié)點可以處 0 個或者最多 16384 個槽。
設置槽指派
通過命令 CLUSTER ADDSLOTS
如 127.0.0.1:7777> CLUSTER ADDSLOTS 1 2 3 4 5 命令就是將 1,2,3,4,5 號插槽指派給本地端口號為 7777 的節(jié)點負責。
設置后節(jié)點將會將槽指派的信息發(fā)送給其他集群,讓其他集群更新信息。
計算鍵屬于哪個槽
- def slot_number(key):
- return CRC16(key) & 16383
計算哈希槽位置其實使用的是 CRC16 算法對鍵值進行計算后再對 16383 取模得到最終所屬插槽。
也可以使用 CLUSTER KEYSLOT
Sharding 流程
- 當客戶端發(fā)起對鍵值對的操作指令后,將任意分配給其中某個節(jié)點
- 節(jié)點計算出該鍵值所屬插槽
- 判斷當前節(jié)點是否為該鍵所屬插槽
- 如果是的話直接執(zhí)行操作命令
- 如果不是的話,向客戶端返回 moved 錯誤,moved 錯誤中將帶著正確的節(jié)點地址與端口,客戶端收到后可以直接轉向至正確節(jié)點
Redis Cluster 的高可用
Redis 的每個節(jié)點都可以分為主節(jié)點與對應從節(jié)點。主節(jié)點負責處理槽,從節(jié)點負責復制某個主節(jié)點,并在主節(jié)點下線時,代替下線的主節(jié)點。
如何實現(xiàn)故障轉移
其實與哨兵模式類似,Redis 的每個節(jié)點都會定期向其他節(jié)點發(fā)送 Ping 消息,以此來檢測對方是否在線。當一個節(jié)點檢測到另一個節(jié)點下線后,會將其設置為疑似下線。如果一個機器中,有半數(shù)以上的節(jié)點將某個主節(jié)點設為疑似下線,則該節(jié)點將會被標記為已下線狀態(tài),并開始執(zhí)行故障轉移。
- 通過 raft 算法從下線主節(jié)點的從節(jié)點中選出新的主節(jié)點
- 被選中的從節(jié)點執(zhí)行 SLAVEOF no one 命令,成為新的主節(jié)點
- 新的主節(jié)點撤銷掉已下線主節(jié)點的槽指派,并將這些槽指給自己
- 新的主節(jié)點向集群中廣播自己由從節(jié)點變?yōu)橹鞴?jié)點
- 新的主節(jié)點開始接受和負責自己處理槽的有關命令請求
總結
本文主要介紹了 Redis 三種集群模式,總結一下
主從模式 可以實現(xiàn)讀寫分離,數(shù)據(jù)備份。但是并不是「高可用」的
哨兵模式 可以看做是主從模式的「高可用」版本,其引入了 Sentinel 對整個 Redis 服務集群進行監(jiān)控。但是由于只有一個主節(jié)點,因此仍然有寫入瓶頸。
Cluster 模式 不僅提供了高可用的手段,同時數(shù)據(jù)是分片保存在各個節(jié)點中的,可以支持高并發(fā)的寫入與讀取。當然實現(xiàn)也是其中最復雜的。