主從機制
基本原理
CAP原理:
- Consistent:一致性
- Availability:可用性
- Partition tolerance:分區容忍性
網絡分區:分佈式節點網絡斷開的場景。
CAP基本原理是:當網絡分區發生時,不能同時保證一致性和可用性。
redis支持主從同步和從從同步:
redis的主從數據是異步的,分佈式的redis不滿足一致性;主從網絡斷開的時候,主節點仍然可以提供服務,因此滿足可用性。redis滿足最終一致性,從節點會努力追趕主節點,最終兩者狀態一致。
同步機制
增量同步:
- 主節點有一個環形隊列,用於存儲改變內存狀態的值
- 主節點會在隊列中不斷寫入改變自己狀態的指令
- 指令不斷髮送給從節點進行同步
注意:如果寫入速度超過讀取速度,則環形隊列值會被新的數據覆蓋
快照同步:
- 主節點進行
bgsave
,輸出存儲到磁盤 - 發送快照到對應的從節點
- 從節點繼續經過增量同步來同步數據
這種方式比較常用,新節點加入redis集羣時,也是全量加載一次,然後增量加載數據
增量和快照同步時,數據都經過磁盤了
無盤複製:直接遍歷內存數據,然後發送給從節點,速度快但是精度太差。
快照同步會影響正在進行AOF的fsync
操作,發生快照同步會推遲fsync
的過程,影響主節點的效率。
wait
指令會讓redis的異步複製變成同步複製,但是阻塞服務,而且如果網絡分區,則redis節點將永遠阻塞。
哨兵機制
問題背景
主從機制的缺陷:
- 主從集羣依賴master的活性,如果master崩潰,則從節點無法同步數據
- 集羣沒有選舉機制,master崩潰後,無法確定新的主節點
問題的本質原因:缺少master奔潰後的選舉很同步機制
解決方案:基本思路是,引入哨兵觀測master健康狀態,如果檢測到master崩潰,則確認新的master,並使用新的master同步集羣數據。
注意,哨兵是配置提供者,而不是代理
引入哨兵後客戶端處理方式:
- 遍歷哨兵集羣,選擇一個可用哨兵服務器。哨兵服務器數據是共享同步的
- 客戶端從哨兵獲取redis集羣的master的地址,之後與master直接通信
- 客戶端連接不上master後,主動請求哨兵,獲取新的地址
- master其它原因出故障,但是客戶端可以與master通信時;哨兵會主動通知客戶端更換
詳細解決方案:
定時監控是解決問題的核心。基本步驟如下:
- 每隔10s,向主節點發送
info
命令,獲取集羣的拓撲結構。不需要知道從節點的地址,主節點會把信息都發來。主節點故障時,利用之前的info的信息,從從節點中選擇一個獲取集羣信息。 - 哨兵節點訂閱
__sentinel__:hello
頻道,獲取其它哨兵對集羣節點的判斷,也獲取其它哨兵信息;同時也發送自己對集羣節點的判斷。 - 每隔1s,哨兵向主節點、從節點和其它哨兵節點做心跳檢測
- 對某個節點,如果最後一次收到的回覆的時間超過配置的時間,則判定爲主觀下線;如果master在客觀下線之前回復了消息,則恢復正常狀態。
- 足夠多的哨兵認爲master下線後,則master爲客觀下線。
- master客觀下線後,哨兵集羣選出哨兵的master,讓哨兵master在redis的slave節點選出主節點,然後指定其它節點向該節點複製信息。
參考資料:
- https://zhuanlan.zhihu.com/p/44474652
- https://juejin.im/post/5c1b482d6fb9a049dd803f55
- https://hellokangning.github.io/zh/post/redis-sentinel-client-connection/
min-slaves-to-write 1 # 至少有一個從節點在寫,否則停止服務
min-slaves-max-lag 10 # 10s內沒有收到從節點的反饋,則認爲是異常,配合上面那個
Redis Cluster 集羣
Redis Cluster把所有的數據分爲16384個槽位,槽位的信息存儲在每個節點中,不需要其它的分佈式存儲空間空間來存儲節點。
客戶端請求redis集羣的兩種方式:
- 簡單方式,直接把請求Key發送到連接的任一個客戶端上。如果key命中了則進行讀寫,否則該服務器會回發MOVE指令和對應的服務器地址x,此時對x服務器操作
- Smart客戶端:保留服務器的映射表,然後客戶端自己計算對應的服務器地址,併發送。可以定時更新表,也可以當請求失敗時更新。
redis的slot和服務的映射關係:
16384個槽有自己對應的映射關係,然後遷移的時候,只需要移動對應槽中的數據即可。使用slot=CRC16(key)/16384
來計算對應的key
屬於哪個槽:
redis-trib是redis的遷移工具,遷移單位是槽,遷移的槽處於中間過渡狀態。
單個key的遷移過程:
- 對當前key執行dump指令序列化
- 通過“客戶端”向目標節點發送數據
- restore指令攜帶序列化的內容作爲參數
- 目標節點反序列化參數
- 目標節點返回“客戶端”OK內容
- 當前節點刪除key,完成遷移
遷移的過程是同步的,執行restore指令到刪除key的時候,主線程是阻塞的。如果遷移過程中出現故障,則下次重新連接工具時,會自動遷移。
集羣環境下,避免產生大key,否則遷移會發生阻塞,影響線上服務。
遷移過程中,客戶端訪問流程:
- 先訪問舊節點,數據存在則直接返回
- 舊節點數據不在,兩種可能:
- 數據在新節點
- 數據根本不存在
對於第二種情況,舊節點返回一個-ASK targetNoodeAddr
的重定向指令,客戶端收到該指令後,去目標節點執行一個不帶參數的ASKING
指令,然後再在目標節點執行原來的查詢指令。
遷移沒完成的時候,新節點無法管理有關的key
,如果此時直接發送查詢指令,新節點不認可該指令,會向客戶端發送-MOVE
指令,讓客戶端再去源節點查詢,如此會形成重定向循環。ASKING
指令是告訴目標節點,必須執行下面的指令。因此遷移影響效率,原來只需要一個ttl,現在需要3個ttl了。
Redis Cluster提供了cluster-node-timeout
,表示節點持續對應的時間失聯時,才認定節點出現故障,並進行主從切換,如果沒有該參數,則網絡抖動可能引起主從頻繁切換。
Redis Cluster是去中心化的,不支持事務,mget
等命令會慢,因爲這是拆分到多個集羣執行的。rename
等方法也不是原子的,會從源節點轉移到目標節點。
槽位變化感知:
MOVED
指令修正槽位,如果客戶端請求的數據不在當前節點,則當前節點返回給客戶端MOVE
指令,並帶有目標節點的地址,此時客戶端會刷新自己的映射表。
ASKING
指令臨時修正槽位,發生再遷移時候,返回給客戶端asking error
。上面提到了,客戶端不會刷新映射表。
2次重試:收到MOVED
指令,去新節點時,新節點正在遷移,返回給客戶端ASKING
的命令了。一般有多次重試的限制次數,超過拋出異常。
集羣變更感知:
- 目標節點掛掉,客戶端拋出
ConnectionError
,立刻隨機挑選一個節點重試,重試的節點通過MOVED
指令通知槽位被分配到了新的節點地址。 - 運維手動更改集羣信息,主節點切換到其它節點,並把舊的節點移除出集羣。在舊節點的指令會獲取
ClusterDown
錯誤,並通知不可用。客戶端關閉所有的連接,清空映射表,向上層報錯。下一條指令來時,會重新初始化信息。
集羣的高可用性
集羣中的每個節點,增加至少一個slave節點作爲備份。當某個節點的master不可用時,添加對應的slave作爲master節點即可。
參考文檔
- https://www.javazhiyin.com/22957.html
- https://www.infoq.cn/article/sIRPs21lbMvDAJtqQRJE
- https://zhuanlan.zhihu.com/p/72056688
- https://medium.com/@pubuduboteju95/deep-dive-into-redis-clustering-1d71484578a9 (推薦)
總結
- redis主從:主從是爲了備份數據,意外情況下可快速恢復
- redis哨兵:爲了保證集羣的可用性
- redis集羣:解決單機容量不夠的問題