5 個維度深度剖析「主從架構」原理

前言

玩過王者榮耀的同學,應該都知道里面有個英雄叫做,她釋放技能時,會出現一個長相一模一樣的分身,而且動作也是一樣的。

那麼我們今天要討論的主從架構原理其實就是多個節點中有一個作爲本體,其他節點作爲分身存在,但是本體和分身的數據都是一樣的,數據總是保持一致,是不是和鏡很相似呢?

爲了保證緩存的高可用,我們經常聽到採用主從架構來保證高可用,那如何去理解主從架構核心原理呢?

這次我們還是用最熟悉的 Redis 緩存來理解主從架構,只要理解了一個主從架構,其他技術的主從架構都是一通百通。

Redis 的主從架構,其實就是利用多副本,將一份數據同時保存在多個實例上。單個實例出現故障後,一般都會過一段時間才能恢復,那麼其他節點還是可以提供服務的。

本篇我會帶着大家一起探討緩存的主從架構幾個問題:

Why:爲什麼需要主從架構?

Redis 單機我們都玩得很溜了,那單點架構會帶來什麼問題?

服務的快速轉移和恢復數據,其實是高可用的範疇,我們就可以通過主從架構來做到了。

What:主從架構原理

文中最開始也提到過這麼一段話,劃重點:

Redis 的主從架構,其實就是利用多副本,將一份數據同時保存在多個實例上。單個實例出現故障後,一般都會過一段時間才能恢復,那麼其他節點還是可以提供服務的。

主從架構拓撲圖

Redis 主從架構其實就是主從庫模式,而主從庫的模式可以分爲三種拓撲結構:一主一從結構、一主多從結構、樹狀主從結構。而如何去構建這種結構其實也很簡單,就是配置下多個節點上 Redis 的配置文件就可以了。如下所示:

slaveof <Master IP> <Master Port>

當然還有一些其他參數配置,就不在本篇講解,下篇主從的部署教程其實已經寫好了,後續發出來。

下面我們接着來看這幾種主從架構:

而對於一主多從結構,還可以再擴展一點:當日常開發中需要執行一些比較耗時的讀命令時,比如 keyssort等,可以用其中一個從節點專門作爲耗時查詢用的從節點,避免慢查詢對主節點造成阻塞,而影響服務的穩定性。我們也可以用圖來進行說明:

主從複製的方式

從節點複製主節點的數據後,就相當於給主從節點備份了,所謂的有備無患就是這個意思。那麼主從複製的原理是怎麼樣的?其實主要就是三種複製方式:持續複製、全量複製、部分複製。

持續複製

當有客戶端的寫命令請求到主節點後,主節點會做兩件事:命令傳播和將寫命令寫入到複製積壓緩衝區

原理圖如下:

全量複製

用於主從節點第一次複製的場景。這在我們的軟件開發中也很常見,比如你要把第三方的用戶數據同步到自己的系統中,一開始肯定是把存量用戶一次性給複製過來,後續有新增或更新的用戶就採用增量更新就可以了。

當然全量複製的時候,數據量很大時,就會對主從節點和網絡造成很大的開銷,也就是常說的複製風暴,所以要避免不必要的全量複製,這個後面再講怎麼避免。

我們先來看下全量複製的原理圖,然後我再來詳細解釋每一步怎麼做的。

全量複製的流程圖

全量複製總共可以分爲 9 步:

(1)從節點發送 psync 命令進行數據同步,會發送 psync 命令,來告訴主節點我想幹啥。

psync 命令格式如下:

psync {runId} {offset}

runId 是 每個 Redis 實例啓動時隨機生成的一個 ID,用來唯一標記這臺 Redis 實例。由於第一次複製時,剛啓動時會隨機生成 runId,只有自己知道,外人是不知道的,所以從節點是不知道主節點的 runId 的,這個時候發送 psync 命令時 runId 就是一個問號。第一次複製時,offset 默認爲 -1。

(2)主節點響應從節點,要開始全量複製了哦。

主節點響應從節點時分三種情況:全量複製、部分複製、舊版全量複製流程,這三種的區別後面會專門講到。

而這個階段就是全量複製:主節點告訴從節點,要開始全量複製了哦。響應如下命令:

FULLRESYNC <runId> <offset>

runId 就是主節點 id,offset 爲複製偏移量。

(3)從節點收到主節點響應的 runId 和 offset,將其保存到從節點本地,這兩個參數以後會用到。

(4)主節點在後臺執行 bgsave 命令保存 RDB 文件到主節點的本地。

(5)主節點將第四步生成的 RDB 文件發送給從節點,子節點收到 RDB 文件後,保存到本地,後面會用到。這個發送的過程也可能直接超時。比如一個 6 GB 的 RDB 文件,100 MB 帶寬下,至少需要 60 秒的傳輸時間,很容易超出默認配置的超時時間。那麼從節點將放棄接收 RDB 文件,並清理已經下載的臨時文件,導致全量複製失敗。所以推薦不要超過 6 GB,如果 RDB 文件實在太大了,可以調大 repl-timeout 超時參數。

(6)在第五步的時候,主節點也沒有閒着,會往另外一個緩衝區寫東西,就是來自客戶端的寫命令數據。這個緩衝區叫做:複製客戶端緩衝區。等第五步完成後,主節點就把這個緩衝區的數據發送給從節點。注意:對於高流量寫入的場景,很容易就把複製客戶端緩衝區給佔滿了,如果 60 秒內緩衝區消耗持續大於 64 MB 或者直接超過 256 MB 時,主節點將直接關閉複製客戶端連接,造成全量不同失敗。

(7)從節點在第五步保存完 RDB 文件後,就會把自身的舊數據清空。

(8)歷經磨難,從節點終於可以開始加載 RDB 文件了,但是對於較大的 RDB 文件,加載 RDB 文件,進行數據恢復,還是非常耗時的,如果從節點負責響應讀命令,則可能拿到過期或錯誤的數據。

(9)從節點加載完 RDB 後,如果當前節點開啓了 AOF 持久化功能,從節點會執行 bgrewriteof 操作,保證 AOF 持久化文件可以立刻使用。

總結下全量複製的步驟:

  1. 從節點給主節點發送命令;

  2. 主節點回復從節點,要開始全量複製了;

  3. 從節點保存主節點信息;

  4. 主節點開始生成 RDB 快照文件;

  5. RDB 文件發給從節點,主節點發送 RDB 文件;

  6. 主節點發送緩存的客戶端命令;

  7. 從節點清空舊數據;

  8. 從節點加載 RDB 文件;

  9. 從節點執行 AOF 操作。

由上面的幾個步驟可以看出,全量複製是非常耗時的,可能比較大的時間開銷如下:

所以除了第一次需要採用全量複製外,其他場景應該避免全量複製的發生。下面介紹另外一種複製方式,可以極大提高複製的效率。

部分複製

這個可以理解爲增量更新,比如和第三方系統對接時,如果第三方有數據更新,定期進行增量更新就可以了。

而 Redis 主從的部分複製就是指當主從之間的網絡故障等原因造成持續複製中斷了,當從節點再次連上主節點後,主節點就補發數據給從節點,避免了全量複製的過高開銷。補發數據的來源就是複製積壓緩衝的數據。

原理圖如下所示:

部分複製總共分爲六步:

(1)當主節點之間失聯後,如果時間超過了 repl-timeout 時間,主節點就認爲從節點發生故障了,中斷連接。

(2)主節點其實一直都在把客戶端寫命令放入複製積壓緩衝區,所以即使斷連了,主節點還是會保留斷連期間的命令,但因爲隊列是固定的,當寫命令太多時,就會導致部分命令被覆蓋了。

(3)主從節點恢復連接。

(4)從節點發送 psync 命令給主節點,帶有 runId 和 offset 參數,runId 是上一次複製時保存的主節點的 runId 值,offset 是從節點的複製偏移量。

(5)主節點接收到從節點的命令後,先判斷傳過來的 runId 是否和自己匹配,如果不匹配,則進行全量複製;如果 runId 匹配,則響應 CONTINUE,告訴從節點,可以進行部分複製了。我要把複製積壓緩衝區的數據發給你了哦,請準備好接收。

(6)主節點根據子節點發送的偏移量,將複製積壓緩衝區的數據發送給子節點。

那複製積壓緩衝區到底是怎麼來根據偏移量來計算要發送哪些緩存數據的呢?我們接着往下看。

複製積壓緩衝區

複製積壓緩衝區有幾個特點:

複製積壓緩衝區

從節點重新連上主節點後,會發送 psync 命令,攜帶着偏移量 offset。比如 offset = 125,然後主節點拿着這個 125 去複製積壓緩衝區找,125 正好在裏面,然後就會執行部分複製的操作,將 125 以後的緩衝數據發送給從節點。

偏移量在複製積壓緩衝區

如果 offset =10,主節點拿着這個 10 去複製積壓緩衝區找,發現隊列中最早的 offset 是 100,所以 100 之前的字節都被覆蓋了,那麼子節點就不能通過複製積壓緩衝區拿到完整數據,所以只能通過全量複製的方式來同步。這個時候主節點就會發送一個 +FULLRESYNC 的命令給子節點,告訴子節點,兄弟,你來得太晚了,只能使用全量同步的方式了。

偏移量在在複製積壓緩衝區

所以爲了合理設置複製積壓緩衝區的大小,有個計算公式推薦給大家:

2 * 恢復連接的時間(s) * 主節點寫緩衝區的速度(MB/s)

比如從節點需要 10 s 才能連上主節點,而主節點在這期間每秒產生 5 MB 的寫數據,那麼複製積壓緩衝區的大小可以設置爲 100 MB。(2 * 10 s * 5 MB/s = 100 MB)

主節點響應從節點 psync 命令

在上面提到從節點不管是全量複製還是部分複製,最開始都會發送一個 psync 命令給主節點,那麼主節點會根據這個命令攜帶的參數 runId 和 offset,來決定如何響應。

原理圖如下所示:

複製時如何保持連接

說完上面主從節點的連接的結構,接下來的問題是這些節點如何在複製時保持連接呢?

也就是說主節點和從節點如何知道對方還存活着?其實就是通過心跳檢測,這個在服務註冊和服務發現裏面經常提到。而主節點和從節點都會向對方發送心跳檢測的命令。

主節點發送命令:

主節點會每隔 10 秒對從節點發送 ping 命令(也就是 10 秒一次心跳),從節點接收到 ping 命令後,會進行響應,所以這個 ping 命令可以用來判斷從節點的存活性和連接狀態。10 秒檢測一次是可以調整的,用參數 repl-ping-slave-period 控制發送頻率。

從節點發送命令:

而從節點也會每隔 1 秒發送一個命令給主節點。這個命令還挺拗口,叫做 REPLCONF ACK {offset} ,作用就是給主節點上報自身當前的複製偏移量的,這個偏移量用來檢查複製數據是否丟失,如果丟失,主節點將補發丟失的數據。

補發丟失數據的操作和部分複製操作的區別:

從節點發送這個命令還可以實時檢測主從節點的網絡狀態,類似於我們在命令行窗口 ping 服務器 ip,如果 ping 不通,則表示雙方的網絡連接有問題。

另外從節點的命令還可以用來計算主從的通信延遲,正常的延遲在 0~1 秒之間,如果超過了默認的 60 秒,則判定從節點下線,斷開復制,等重新上線後,複製繼續。而斷開期間丟失的數據,則可能需要全量複製或部分複製,取決於從節點的 offset 偏移量在不在複製積壓緩衝區。

Who:誰需要關心主從架構?

運維人員肯定是要關注,然後就是架構師和技術專家,他們對這些技術是需要有深入瞭解的。當然掌握了主從複製原理,其他技術棧的複製原理也是類似,甚至在業務場景下,和第三方同步數據也可以用到裏面的思想。

When:什麼時候用主從架構?

其實 Redis 有更好的高可用架構方案:集羣部署。

而集羣部署就是將多個主從結構進行橫向擴容 + 分片存儲 + 哨兵。這裏面的幾個名詞放到後面的文章再講,本篇主要針對主從架構。

所以真實的大型電商的生產環境一般都是採取集羣部署的方式,一些規模較小的系統直接單機就可以搞定,再提高點可用性的話,就用主從架構,實現讀寫分離。其實架構就是綜合考慮學習成本 + 部署成本 + 維護成本得到的一種最合適的方案。一味追求高可用,不結合實際的話,用處不大,這裏推薦大家看下《架構整潔之道》這本書。

Question:主從架構會帶來哪些問題?

爲什麼主從庫之間的複製不使用 AOF?

原因:

主從架構的高可用問題

因爲主從架構只有一個主節點,所以主節點宕機後,影響整個系統運行。

如何監控主節點?用 Redis 哨兵機制。

如何增加主節點?用 Redis 集羣。

主從讀寫分離坑不坑?

主從設置爲讀寫分離後,需要客戶端綁定主節點進行讀寫,綁定從節點進行讀,這個是比較麻煩的事情。

主從讀寫架構就不能自行判斷往哪個服務器讀,哪個服務器寫嗎?其實需要我們自己來控制。

系統維護

主從模式肯定比單機複雜,維護成本也較高。

總結

本篇從宏觀和微觀上講解了主從架構的原理:

微觀視角:涉及了主從架構的三種拓撲結構來應對不同的場景,然後深入講解了持續複製、全量複製、部分複製的區別。以及大家特別關心的複製積壓緩衝區,客戶端緩衝區的使用場景,以及會遇到的坑。

宏觀視角:講解了主從架構誰需要來關心、什麼時候用、以及主從架構會帶來的問題。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/VGKSAzcV9hzfb-xd42lOug