簡讀筆記-Redis設計與實現第三章

你的頭髮真的好長發表於2019-04-23

第三部分 多機資料庫的實現

複製

1.舊版複製功能的實現

  • 舊版複製分為兩個階段 : 同步命令傳播

  • 同步過程的執行步驟

    • 從伺服器向主伺服器傳送SYNC命令
    • 收到SYNC命令後,主伺服器開始執行BGSAVE操作生成RDB檔案,並使用一個緩衝區記錄現在開始執行的所有寫命令(用於命令傳播階段保持資料庫一致性)。
    • 當主伺服器的BGSAVE操作執行完時,主伺服器會將BGSAVE命令生成的RDB檔案傳送給從伺服器,從伺服器接收並載入這個RDB檔案,將自己的資料庫狀態更新至主伺服器執行BGSAVE命令時的資料庫狀態。
    • 主伺服器將記錄在緩衝區裡面的所有寫命令傳送給從伺服器,從伺服器執行這些寫命令。將自己資料庫狀態更新至主伺服器資料庫當前狀態。
  • 命令傳播的過程

    • 主伺服器將執行的寫命令,傳送給從伺服器執行,當從伺服器執行了相同寫命令後,主從伺服器將再次回到一致性狀態
  • SYNC命令流程圖

    簡讀筆記-Redis設計與實現第三章

2.舊版複製功能的缺陷

  • 在從伺服器A斷線的情況(此時儲存了A1 - A99鍵值對),當伺服器重連上主伺服器時 (此時主伺服器儲存到了A120), 從伺服器會再次向主伺服器傳送SYNC命令,此時主伺服器又需要將資料庫的所有鍵值對儲存到RDB檔案中(A1-A120鍵值對),然而從伺服器A其實只需要掉線時期產生的A100-A120資料就能恢復成一致性狀態。 所以為了讓從伺服器補足一小部分缺失的資料,而讓主伺服器重新執行一次BGSAVE操作,造成斷線重連後的效率會很低。

3.新版複製功能的實現

  • 如何提高斷線重連後同步的效率呢? 實際上我們只需要儲存斷線後的那部分寫命令,重連後讓主伺服器再傳送過來給從伺服器執行就好了。
  • 新版複製解決了舊版複製在處理斷線重連時的低效情況。使用PSYNC命令代替SYNC命令執行復制時的同步操作。
  • PSYNC命令具有完整重同步部分重同步兩種模式
    • 完整重同步用於處理初次複製的情況:與SYNC命令的執行步驟基本一致 , 都是通過讓主伺服器建立併傳送RDB檔案,以及向從伺服器傳送儲存在緩衝區裡面的寫命令來進行同步。
    • 部分重同步用於處理斷線後重複製的情況:當從伺服器在斷線後重新連線主伺服器時,如果條件允許,主伺服器可以將主從伺服器連線斷開期間執行的寫命令傳送給從伺服器,從伺服器只要接收並執行這些寫命令,就可以將資料庫更新至主伺服器當前所在狀態了。
  • 由於部分重同步只需要將伺服器缺少的寫命令傳送給從伺服器執行舊可以了,因此所需資源更少,速度更快。

4.部分重同步的實現

  • 複製偏移量

    • 主伺服器和從伺服器都維護一個複製偏移量。 主伺服器每次向從伺服器傳播N個位元組的資料時,就將自己的複製偏移量的值加上N。

    • 從伺服器每次收到主伺服器傳播來的N個位元組的資料時,就將自己的複製偏移量加上N

    • 可以很容易通過判斷主從伺服器的複製偏移量來判斷是否處於一致的狀態

      簡讀筆記-Redis設計與實現第三章

    如果發現偏移量不一致時,如何判斷接下來要進行完整重同步 or 部分重同步。主伺服器又如何補償掉線期間丟失的資料呢?與複製積壓緩衝區有關。

  • 複製積壓緩衝區

    • 複製積壓緩衝區維護一個固定長度的先進先出佇列,預設大小為1MB

      簡讀筆記-Redis設計與實現第三章

    • 當伺服器進行命令傳播時,不僅將命令傳送給所有從伺服器,還將寫命令入隊到複製積壓緩衝區中

      簡讀筆記-Redis設計與實現第三章

    • 當從伺服器重連主伺服器時,從伺服器通過PSYNC命令將複製偏移量offset也傳送給主伺服器,如果offset偏移量之後的資料仍存在複製積壓緩衝區中,則執行部分重同步操作 ; 而如果offset偏移量之後的資料已不存在複製積壓緩衝區中(掉線太久),則執行完整重同步操作。

    • 複製積壓緩衝區的最小大小應為 平均重連時間 * 主伺服器每秒產生的寫命令數量 ; 為了保證大部分掉線情況都能使用部分重同步, 應將複製積壓緩衝區設為 2*平均重連時間 * 主伺服器每秒產生的寫命令數量

  • 伺服器執行ID

    • 用於記錄判斷 斷線重連後的伺服器是否為 原先連線的伺服器
    • 當從伺服器對主伺服器進行初次複製時,主伺服器會將自己的執行ID傳送給從伺服器,而從伺服器則會將這個ID儲存起來。 當從伺服器重新連線時,從伺服器向主伺服器傳送之前儲存的伺服器執行ID
      • 如果從伺服器儲存的ID和 當前連線的主伺服器執行ID相同,則說明斷線前複製的就是當前的主伺服器,則再根據複製積壓緩衝區判斷進行部分重同步 還是 完整重同步
      • 相反,則說明線前複製的不是當前的主伺服器,則需要進行完整重同步操作。

5.PSYNC命令的實現

  • PSYNC : runid表示上一次複製的主伺服器執行ID , offset表示當前複製偏移量
  • 主伺服器返回 FULLRESYNC : 表示執行完整重同步操作,offset作為從伺服器的初始偏移量
  • 主伺服器返回 CONTINUE : 表示執行部分重同步操作。 只需等待主伺服器將缺失資料重發即可。

6.複製的實現

SLAVEOF <master_ip> <master_port>

  1. 設定主伺服器的地址和埠號
  2. 建立套接字連線(讓從伺服器成為主伺服器客戶端)
  3. 傳送PING命令 (檢測主伺服器連線狀況)
  4. 身份驗證
  5. 傳送埠資訊(讓主伺服器儲存從伺服器的相關資訊)
  6. 同步(傳送PSYNC命令,執行同步操作。 並且由單工通訊程式設計雙工通訊,即主從伺服器互相成為對方的客戶端)
  7. 命令傳播
  8. 心跳檢測(從伺服器會每秒1次的頻率,向主伺服器傳送命令 REPLCONF ACK <從伺服器偏移量>
    • 檢測主從伺服器網路連線狀態
    • 複製實現min-slaves功能 (當主伺服器的從伺服器數量小於min-slaves時,則認為叢集掛了)
    • 檢測命令丟失(主伺服器進行部分重同步時由於網路問題而丟失,則通過心跳檢測到後進行補發)

總結

  • 當客戶端向從伺服器傳送SLAVEOF命令,要求從伺服器複製主伺服器時,從伺服器首先需要執行同步操作。將從伺服器的資料庫叢臺更新至主伺服器當前所處的資料庫狀態。 在同步操作完成後,這種一致性的狀態不是一成不變的,當主伺服器執行客戶端的寫命令時,主從伺服器的狀態舊不再一致, 因此主伺服器需要對從伺服器執行命令傳播操作, 即主伺服器將執行的寫命令,傳送給從伺服器執行。
  • Redis 2.8以前的複製功能不能高效地處理斷線後重複製情況,但Redis2.8新新增的部分重同步功能可以解決這個問題。
  • 部分重同步通過複製偏移量複製積壓緩衝區伺服器執行ID三個部分來實現。
  • 在複製操作剛開始的時候,從伺服器會成為主伺服器的客戶端,並通過向主伺服器傳送命令請求來執行復制步驟。 而在複製的後期(同步階段),主從伺服器會互相成為對方的客戶端 (主伺服器傳送命令(命令傳播,部分重同步)給從伺服器來維護資料一致性)
  • 主伺服器通過向從伺服器傳播命令來更新從伺服器的狀態,保證主從伺服器一致,而從伺服器則通過向主伺服器傳送命令來進行心跳檢測,以及命令丟失檢測。

Sentinel哨兵機制

Sentinel是Redis的高可用性解決方案:由一個或多個Sentinel例項組成的Sentinel系統可以監視任意多個主伺服器,以及這些主伺服器下的從伺服器,並在被監視的主伺服器進行下線狀態時,自動將被下線主伺服器下的某個重伺服器升級為主伺服器,然後由新的主伺服器代替已下線的主伺服器繼續處理命令請求(失效轉移

Sentinel的構成

  • Setinel啟動時的執行步驟

    • 初始化伺服器(Sentinel本質上只是執行在特殊模式下的Redis伺服器, 部分初始化過程不相同)
    • 將普通Redis伺服器使用的程式碼替換成Sentinel專用程式碼(如沒有get,set 而有ping,INFO)
    • 初始化Sentinel狀態
    • 初始化Sentinel的監視主伺服器列表
    • 建立連向主伺服器的網路連線 (建立命令連線和訂閱連線; 前者用於向主伺服器傳送命令 。 而後者是訂閱主伺服器的 _sentinel_hello頻道,用於接收其他主伺服器資訊的變更的頻道資訊)
  • Setineal通過讀取配置檔案,使用字典記錄所有的Master; 並通過給主伺服器傳送INFO命令,得出主伺服器下的所有從伺服器資訊。

簡讀筆記-Redis設計與實現第三章

如何獲取主伺服器資訊

  • Sentinel預設會以每10秒一次的頻率,通過命令連線向被監視的主伺服器傳送INFO命令,並通過分析INFO命令的回覆來獲取主伺服器的當前資訊(狀態,從伺服器slaves資訊)

如何獲取從伺服器資訊

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

    • 建立命令連線後,每10秒向從伺服器傳送INFO命令

      • 獲取從伺服器的執行ID,角色,優先順序,偏移量, 該從伺服器對應的主伺服器連線資訊,狀態

向主從伺服器傳送訊息

在預設情況下,Sentinel會以每兩秒一次的頻率,通過命令連線向所有被監視的主伺服器和從伺服器傳送訂閱訊息

PUBLISH sentinel_:hello <sentinel的資訊,主伺服器的資訊>

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

  • 當Sentinel與一個主伺服器或者從伺服器建立建立起訂閱連線之後,Sentinel就會通過訂閱連線,向伺服器傳送以下命令。一直持續到Sentinel與伺服器的連線斷開為止。

  • 對於監視同一個伺服器的多個Sentinel來說,一個Sentinel傳送的資訊會被其他Sentinel接收到,這些資訊會被用於更新其他Sentinel對傳送資訊Sentinel的認知,也會被用於更新其他Sentinel對被監視伺服器的認知

簡讀筆記-Redis設計與實現第三章

檢測主觀下線狀態

  • 在預設情況下,Sentinel會以每秒一次的頻率向所有與它建立了命令連線的例項(包括主伺服器,從伺服器,其他Sentinel在內)傳送PING命令,並通過例項返回的PING命令回覆來判斷例項是否線上。
  • Sentinel配置檔案中的 down-after-milliseconds指定了判斷例項主觀下線所需的時間長度。如果在down-after-milliseconds時間內連續傳送了無效回覆,則Sentinel將判定該例項主觀下線,並將對應的例項結構的flags設定為 SRI_S_DOWN。 (Subjective 主觀)

檢測客觀下線狀態

  • 當Sentinel將一個主伺服器判斷為主觀下線後,為了確認這個主伺服器是否真的下線了,它會向同樣監視這一主伺服器的其他Sentinel進行詢問,看它們是否也認為主伺服器已經進入了下線狀態。 當Sentinel從其他Sentinel那裡接收到足夠數量的已下線判斷後,Sentinel就會將從伺服器判斷為客觀下線,並將主伺服器執行故障轉移操作。
  • 當判斷為客觀下線後,將flags設定為 SRI_O_DOWN (Objective 客觀)

如何選舉領頭Setinel

  • 當一個主伺服器被判斷為客觀下線時,監視這個下線主伺服器的各個Sentinel會進行協商,選舉出一個領頭Sentinel,並由領頭Sentinel對下線主伺服器執行故障轉移操作
##請求格式
Sentinel is-master-down-by-addr  <master的ip地址> <埠號>  <配置紀元>(標誌是否同一輪選舉) <SentinelId>

##響應格式
<上下線狀態> <區域性領頭id> <當前紀元>
複製程式碼

故障轉移

  1. 在已下線主伺服器屬下的所有從伺服器裡面,挑選出一個從伺服器,並將其轉換為主伺服器(升級
  2. 讓已下線主伺服器屬下的所有從伺服器改為複製新的主伺服器
  3. 將已下線主伺服器設定為新的主伺服器的從伺服器,當這個舊的伺服器重新上線時,它就會成為新的主伺服器的從伺服器(降級

叢集

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

主要內容有叢集中的節點,槽指派,命令執行,重新分片,轉向,故障轉移,訊息等方面

  • 節點通過握手來將其他節點新增到自己所處的叢集當中 (clustermeet ip port)

  • 叢集中的16384個槽可以分別指派給叢集中的各個節點,每個節點都會記錄哪些槽指派給了自己,哪些槽又被指派給了其他節點。

  • clusterState.slots陣列記錄了叢集中所有槽的指派資訊,使得能以O(1)時間判斷某個槽指派給了哪個節點;而clusterNode.slots陣列只記錄了clusterNode結構所代表的節點的槽指派資訊。使得交換槽資訊時只需傳送clusterNode.slots陣列,讓接收資訊的節點更新字典表中對應的clusterNode即可。而不用遍歷clusterState中所有元素,標誌當前節點所指派的槽,再將其資訊進行傳送,時間複雜度為O(n)。下圖為節點7000建立的clusterState結構。

簡讀筆記-Redis設計與實現第三章

  • 節點在接收到一個命令請求時,會先檢查這個命令請求要處理的鍵所在的槽是否由自己負責(檢視clusterState.slots陣列),如果不是的話,節點將向客戶端返回一個MOVED操作, MOVED錯誤攜帶的資訊可以指引客戶端轉向至正在負責相關槽的節點。

  • 節點還會使用跳躍表來儲存槽與鍵之間的關係 (方便比如 檢視 屬於某個slot的資料庫鍵由哪些的命令)

  • 對Redis叢集的重新分片工作是由redis-trib負責的,重新分片的關鍵是將屬於某個槽的所有鍵值對從一個節點轉移到另一個節點。

    簡讀筆記-Redis設計與實現第三章

  • 如果節點A正在遷移槽i到節點B,那麼當節點A沒能在自己的資料庫中找到命令指定的資料庫鍵時,節點A會向客戶端返回一個ASK錯誤,指引客戶端到節點B繼續查詢指定的資料庫鍵。

  • MOVED錯誤表示槽的負責權已經從一個節點轉移到了另一個節點,而ASK錯誤只是兩個節點在遷移槽的過程中使用的一種臨時措施。

  • 叢集裡的從節點用於複製主節點,並在主節點下線時,代替主節點繼續處理請求。

    簡讀筆記-Redis設計與實現第三章

  • 叢集中的節點通過傳送和接收訊息來通訊,常見的訊息包括MEET,PING,PONG,PUBLISH,FAIL五種。

相關文章