1. 程式人生 > >Redis 設計與實現[3] -- 多機資料庫的實現

Redis 設計與實現[3] -- 多機資料庫的實現

1 複製

在 Redis 中,使用者可以通過 slaveof 命令讓一個伺服器去複製另一個伺服器,被複制的伺服器為主伺服器(master),對主伺服器進行復制的伺服器為從伺服器(slave),主伺服器和從伺服器將保持相同的資料。

1.1 舊版複製功能

Redis 的複製功能分為同步(sync)和命令傳播(command propagate)兩個操作:

  • 同步操作用於將從伺服器的資料庫狀態更新至主伺服器當前所處的資料庫狀態
  • 命令傳播操作則用於在主伺服器的資料庫狀態被修改,導致主從伺服器的資料庫狀態出現不一致時,讓主伺服器的資料庫重新回到一致狀態

同步操作:

命令傳播:同步操作後,客戶端向主伺服器傳送的命令操作需要由主伺服器向從伺服器傳送相同的命令

存在的問題:如果處於命令傳播階段的從伺服器因為網路原因而中斷了複製,則需要重連伺服器後重頭寫入 RDB 檔案,這是非常低效的。

1.2 新版複製功能

為了解決舊版複製的問題,Redis 2.8 之後使用 PSYNC 命令來替代 SYNC 命令執行復制時的同步操作。

PSYNC 命令具有完整重同步和部分重同步。

完整重同步跟上面一樣,部分重同步演算法如下所示:

部分重同步功能實現由三部分構成:主伺服器的複製偏移量和從伺服器的複製偏移量、主伺服器的複製積壓緩衝區和伺服器的執行ID

1.3 複製功能實現

在複製操作剛開始的時候,從伺服器會成為主伺服器的客戶端,並通過向主伺服器傳送命令請求來執行復制步驟,而在複製操作的後期,主從伺服器會相互成為對方的客戶端。

1.4 心跳檢測

在命令傳播階段,從伺服器預設會以每秒一次的頻率,向主伺服器傳送命令:

REPLCONF ACK <replication_offset>

其主要作用為:

  • 檢測主從伺服器的網路連線狀態
  • 輔助實現 min-slaves 選項
  • 檢測命令丟失

2 哨兵

哨兵是 Redis 的高可用解決方案:由一個或多個哨兵例項組成的哨兵系統可以監視任意多個主伺服器,以及這些主伺服器屬下的所有從伺服器,並在被監視的主伺服器進入下線狀態時,自動將下線主伺服器屬下的某個從伺服器升級為新的主伺服器,然後由新的主伺服器替代已下線的主伺服器繼續執行命令。

2.1 哨兵機制

下圖展示了一個哨兵系統監視伺服器的例子:

  • 雙環圖案表示的是當前的主伺服器 server1
  • 單環圖案表示的是三個從伺服器
  • 從伺服器正在複製主伺服器,而哨兵系統則正在監視所有四個伺服器

下圖顯示,哨兵系統覺察到 server1 已經下線:

故障轉移:

  • 哨兵系統挑選 server1 屬下的其中一個從伺服器,並將這個被選中的從伺服器升級為新的主伺服器
  • 哨兵系統會向 server1 屬下的所有從伺服器傳送新的複製指令,讓它們成為新的主伺服器的從伺服器,當所有從伺服器都開始複製新的主伺服器時,故障轉移操作執行完畢。
  • 哨兵系統還會繼續監視已下線的 server1,並在它重新上線時,將它設定為新的主伺服器的從伺服器,如下兩張圖所示

2.2 故障轉移原理及實現

啟動並初始化哨兵(執行在特殊模式下的 Redis 伺服器)redis-sentinel /path/to/your/sentinel.conf

  • 初始化伺服器
  • 將普通 Redis 伺服器使用的程式碼替換成 Sentinel 專用程式碼
  • 初始化 Sentinel 狀態
  • 根據給定的配置檔案,初始化 Sentinel 的監視主伺服器列表
  • 建立連向主伺服器的非同步網路連線(命令連線和訂閱連線,保證斷線不丟失資訊)

獲取主伺服器的資訊

Sentinel 預設每十秒一次的頻率,通過命令連線向被監視的主伺服器傳送 INFO 命令,並通過分析 INFO 命令的回覆來獲取主伺服器本身的資訊和主伺服器所屬從伺服器的資訊。

獲取從伺服器資訊

當 Sentinel 發現主伺服器有新的從伺服器出現時,Sentinel 除了會為這個新的從伺服器建立相應的例項結構之外,Sentinel 還會建立連線到從伺服器的命令連線和訂閱連線。

向主伺服器和從伺服器傳送資訊

在預設情況下,Sentinel 會以兩秒一次的頻率,通過命令連線所有被監視的主伺服器和從伺服器傳送命令。而這個命令會向伺服器的_sentinel_:hello頻道傳送一條包含 Sentinel 本身和主伺服器資訊的資訊。

接收來自主伺服器和從伺服器的頻道資訊

當 Sentinel 與一個主伺服器或者從伺服器建立訂閱連線後,Sentienl 向伺服器通過命令連線傳送資訊到頻道,Sentinel 通過訂閱連線從頻道中接收來自伺服器的資訊

檢測主觀下線狀態

Sentinel 會以每秒鐘一次的頻率向所有與它建立了命令連線的例項(主伺服器、從伺服器、其他Sentinel)傳送 PING 命令,並通過例項返回的 PING 回覆來判斷例項是否線上。

判斷客觀下線狀態

當 Sentinel 將一個主伺服器判斷為主觀下線後,為了確認這個主伺服器是否真的下線了,它會向同樣監視這一主伺服器的其他 Sentinel 進行詢問,來確定下線狀態。

選舉領頭 Sentinel

  • 所有線上的 Sentinel 都有被選為領頭 Sentinel 的資格
  • 每次進行領頭 Sentinel 選舉後,不論是否選舉成功,所有 Sentinel 的配置紀元(計數器)的值都會自增一次
  • 在一個配置紀元裡面,所有的 Sentinel 都有一次將某個 Sentinel 設定為區域性領頭 Sentinel 的機會,並且區域性領頭一旦設定,這個配置紀元裡面就不能再更改
  • 每個發現主伺服器進入客觀下線的 Sentinel 都會要求其他 Sentinel 將自己設定為區域性領頭 Sentinel
  • Sentinel 設定區域性領頭 Sentinel 的規則是先到先得:最先向目標 Sentinel 傳送設定要求的源 Sentinel 將稱為目標 Sentinel 的區域性領頭 Sentinel,之後的所有設定要求都會被拒絕
  • 如果某個 Sentinel 被半數以上的 Sentinel 設定成區域性領頭 Sentinel,那麼這個 Sentinel 成為領頭 Sentinel

選舉新的主伺服器

領頭 Sentinel 會將已經下線的主服務的所有從伺服器儲存到一個列表裡面,然後按照如下規則過濾選舉:

  • 刪除列表中所有處於下線或者斷線狀態的從伺服器
  • 刪除列表中所有最近五秒內沒有回覆過領頭 Sentinel 的 INFO 命令的從伺服器
  • 刪除所有與已下線主伺服器連線斷開超過 down-after-milliseconds * 10 的從伺服器,down-after-milliseconds 為判斷下線時間
  • 之後按照最高優先順序、複製偏移量最大,執行 ID 最小的從服務選為新的主伺服器

3 叢集

Redis 叢集是 Redis 提供的分散式資料庫方案,叢集通過分片(sharding)來進行資料共享,並提供複製和故障轉移功能。

3.1 節點

一個 Redis 叢集通常由多個節點(node)組成,叢集就是將各個節點相互連線起來。向一個節點 node 傳送CLUSTER MEET <ip> <port>命令,可以讓 node 節點與 ip 和port 所指定的節點進行握手,當握手成功時,node 節點就會將 ip 和 port 所指定的節點新增到 node 節點當前所在的叢集中。

每個節點都使用一個 clusterNode 結構來記錄自己的狀態(節點的建立時間,節點的名字,節點的當前配置紀元,節點的 IP 地址和埠號),併為叢集中的所有其他節點(包括主節點和從節點)都建立一個相應的 clusterNode 結構,以此來記錄其他節點的狀態。

CLUSTER MEET 命令

3.2 槽指派

Redis 叢集通過分片的方式來儲存資料庫中的鍵值對:叢集的整個資料庫被分為 16384 個槽(slot),資料庫中的每個鍵都屬於這 16384 個槽的其中一個,叢集中的每個節點可以處理 0 個或最多 16384 個槽。當資料庫中的 16384 個槽都有節點在處理時,叢集處於上線狀態;相反,如果資料庫中有任何一個槽沒有得到處理,那麼叢集就處於下線狀態。

一個節點除了會將自己負責處理的槽記錄在 clusterNode 結構的 slots 屬性和 numslots 屬性之外,它還會將自己的 slots 陣列通過訊息傳送給叢集中的其他節點,以此來告知其他節點自己目前負責處理哪些槽。

3.3 在叢集中執行命令

當客戶端向節點發送與資料庫鍵有關的命令時,接收命令的節點會計算出命令要處理的資料庫鍵屬於哪個槽,並檢查這個槽是否指派給了自己:

  • 如果鍵所在的槽正好就指派給了當前節點,那麼節點直接執行這個命令
  • 如果鍵所在的槽沒有指派給當前節點,那麼節點會向客戶端返回一個 MOVED 錯誤,指引客戶端轉向至正確的節點,並再次傳送之前想要執行的命令。

3.4 重新分片

Redis 叢集的重新分片操作可以將任意數量已經指派給某個節點的槽改為指派給另一個節點,並且槽相關的鍵值對也會從源節點被移動到目標節點,Redis 叢集的重新分片操作是由 Redis 的叢集管理軟體 redis-trib 負責執行的,具體過程如下:

3.5 ASK

在進行重新分片期間,源節點向目標節點遷移一個槽的過程中,可能會出現這種情況:屬於被遷移槽的一部分鍵值對儲存在源節點裡面,而另一部分鍵值對則儲存在目標節點裡面。

當客戶端向源節點發送一個與資料庫鍵有關的命令,並且命令要處理的資料庫鍵恰好就屬於正在被遷移的槽時:

  • 源節點會先在自己的資料庫裡面查詢指定的鍵,如果找到的話,就直接執行客戶端傳送的命令
  • 如果源節點沒能在自己的資料庫裡面找到指定的鍵,那麼這個鍵就可能被遷移到目標節點,源節點向客戶端返回一個 ASK 錯誤,指引客戶端轉向正在匯入槽的目標節點,並再次傳送之前想要執行的命令。

3.6 複製和故障轉移

Redis 叢集中的節點分為主節點和從節點,其中主節點用於處理槽,而從節點則用於複製某個主節點,並在被複制的主節點下線時,替代下線主節點繼續處理命令請求

下線節點的故障轉移

  • 複製下線節點的所有從節點,會有一個從節點被選中
  • 被選中的從節點會執行 SLAVEOF no one 命令,成為新的主節點
  • 先的主節點會撤銷所有對已下線主節點的槽指派,並將這些槽全部指派給自己
  • 新的主節點向叢集廣播一條 PONG 訊息,這條 PONG 訊息可以讓叢集中的其他節點立即知道這個節點已經由從節點變成主節點,並且這個節點已經接管了原本由已下線節點負責處理的槽
  • 新的主節點開始接收和自己負責處理的槽有關的命令請求,故障轉移完成

選舉新的主節點

  • 叢集的配置紀元是一個自增計數器,初始值為 0
  • 當叢集中的某個節點開始一次故障轉移操作時,叢集配置紀元的值會被增1
  • 對於每個配置紀元,叢集裡面每個負責處理槽的主節點都有一次投票機會,而第一個向主節點要求投票的從節點將會獲得主節點的投票
  • 當從節點發現自己正在複製的主節點進入已下線狀態時,從節點會向叢集廣播一條訊息,要求所有收到這條訊息,並且具有投票權的主節點向這個從節點投票
  • 如果一個主節點具有投票權,並且這個主節點尚未投票給其他從節點,那麼主節點將會向要求投票的從節點返回一條訊息,表示這個主節點支援從節點成為新的主節點
  • 每個參與選舉的從節點根據收到的訊息統計自己獲得多少主節點的支援
  • 如果叢集中有 N 個具有投票權的主節點,那麼當一個從節點收集到大於等於 N/2+1 張支援票時,這個從節點就會被選為新的主節點
  • 由於在每個配置紀元裡,每個主節點只能投票一次,則保證了新的主節點只會有一個
  • 如果此輪沒有得到過半數的支援票,則叢集進入一個新的配置紀元,再次選舉

3.7 訊息

叢集中的各個節點通過傳送訊息和接受訊息來進行通訊,節點發送的訊息主要有五種:MEET(加入叢集)PING(線上檢測)PONG(確認訊息到達)FALL(節點下線)PUBLISH(執行相同指令)

 

文章來源:https://gongfukangee.github.io/2018/08/14/Redis-3/