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

那麼我們今天要讨論的主從架構原理其實就是多個節點中有一個作為本體,其他節點作為分身存在,但是本體和分身的資料都是一樣的,資料總是保持一緻,是不是和鏡很相似呢?
為了保證緩存的高可用,我們經常聽到采用主從架構來保證高可用,那如何去了解主從架構核心原理呢?
這次我們還是用最熟悉的 Redis 緩存來了解主從架構,隻要了解了一個主從架構,其他技術的主從架構都是一通百通。
Redis 的主從架構,其實就是利用多副本,将一份資料同時儲存在多個執行個體上。單個執行個體出現故障後,一般都會過一段時間才能恢複,那麼其他節點還是可以提供服務的。
本篇我會帶着大家一起探讨緩存的主從架構幾個問題:
- Why:為什麼需要主從架構?
- What:主從架構原理?
- Who:誰需要關心主從架構?
- When:什麼時候用主從架構?
- Question:主從架構會帶來哪些問題?
Why:為什麼需要主從架構?
Redis 單機我們都玩得很溜了,那單點架構會帶來什麼問題?
- 造成服務雪崩:高并發場景下,Redis 單點故障了,導緻請求到 Redis 後都傳回錯誤,或者請求都到資料庫了,造成服務雪崩,這是不能接受的。
- 不能進行快速轉移:線上系統,Redis 單點故障了,沒有其他的備份節點可用。如何做到故障快速轉移?
- 不能快速恢複資料:Redis 是記憶體資料庫,記憶體資料會自動備份到 RDB 和 AOF 檔案(開啟了兩種持久化的情況下),當某個節點出現故障時,能将這些備份檔案快速恢複到節點,将故障造成的影響降到最低。
服務的快速轉移和恢複資料,其實是高可用的範疇,我們就可以通過主從架構來做到了。
What:主從架構原理
文中最開始也提到過這麼一段話,劃重點:
Redis 的主從架構,其實就是利用多副本,将一份資料同時儲存在多個執行個體上。單個執行個體出現故障後,一般都會過一段時間才能恢複,那麼其他節點還是可以提供服務的。
主從架構拓撲圖
Redis 主從架構其實就是主從庫模式,而主從庫的模式可以分為三種拓撲結構:一主一從結構、一主多從結構、樹狀主從結構。而如何去建構這種結構其實也很簡單,就是配置下多個節點上 Redis 的配置檔案就可以了。如下所示:
slaveof <Master IP> <Master Port>
當然還有一些其他參數配置,就不在本篇講解,下篇主從的部署教程其實已經寫好了,後續發出來。
下面我們接着來看這幾種主從架構:
- 一主一從結構:一個主節點,一個從節點,主節點可讀可寫,從節點隻接收讀請求。常用于主節點出現故障時,從節點能夠快速頂上。
- 一主多從結構:一個主節點,多個從節點,對于讀指令較大的場景,可以把讀指令分攤到多個從節點。
而對于一主多從結構,還可以再擴充一點:當日常開發中需要執行一些比較耗時的讀指令時,比如
keys
、
sort
等,可以用其中一個從節點專門作為耗時查詢用的從節點,避免慢查詢對主節點造成阻塞,而影響服務的穩定性。我們也可以用圖來進行說明:
- 樹狀主從結構:一個主節點,多個從節點,其中一個從節點作為中間層,既可以複制主節點,又可以當做其他從節點複制的主節點。有效降低主節點負載和需要傳送給從節點的資料量。
主從複制的方式
從節點複制主節點的資料後,就相當于給主從節點備份了,所謂的有備無患就是這個意思。那麼主從複制的原理是怎麼樣的?其實主要就是三種複制方式:持續複制、全量複制、部分複制。
持續複制
當有用戶端的寫指令請求到主節點後,主節點會做兩件事:
指令傳播
和将寫指令寫入到
複制積壓緩沖區
。
原理圖如下:
- 指令傳播:将寫指令持續發送給所有從伺服器,保持主從資料一緻。這個就可以了解為持續複制了。
- 複制積壓緩沖區:其實就是一個有界隊列,儲存着最近傳播的寫指令,而隊列裡面的每個位元組都有一個偏移量辨別。複制積壓緩沖區的作用和原理在部分複制的時候再細講。
全量複制
用于主從節點第一次複制的場景。這在我們的軟體開發中也很常見,比如你要把第三方的使用者資料同步到自己的系統中,一開始肯定是把存量使用者一次性給複制過來,後續有新增或更新的使用者就采用增量更新就可以了。
當然全量複制的時候,資料量很大時,就會對主從節點和網絡造成很大的開銷,也就是常說的
複制風暴
,是以要避免不必要的全量複制,這個後面再講怎麼避免。
我們先來看下全量複制的原理圖,然後我再來詳細解釋每一步怎麼做的。
全量複制的流程圖
全量複制總共可以分為 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 持久化檔案可以立刻使用。
“
總結下全量複制的步驟:
”
- 從節點給主節點發送指令;
- 主節點回複從節點,要開始全量複制了;
- 從節點儲存主節點資訊;
- 主節點開始生成 RDB 快照檔案;
- RDB 檔案發給從節點,主節點發送 RDB 檔案;
- 主節點發送緩存的用戶端指令;
- 從節點清空舊資料;
- 從節點加載 RDB 檔案;
- 從節點執行 AOF 操作。
由上面的幾個步驟可以看出,全量複制是非常耗時的,可能比較大的時間開銷如下:
- 第四步,主節點 fork 出子程序執行 bgsave 時,fork 操作耗時。
- 第五步,RDB 檔案的網絡傳輸時間。
- 第七步,從節點清空資料花時間。
- 第八步,從節點加載 RDB 的時間。
- 第九步,AOF 的重寫時間。
是以除了第一次需要采用全量複制外,其他場景應該避免全量複制的發生。下面介紹另外一種複制方式,可以極大提高複制的效率。
部分複制
這個可以了解為增量更新,比如和第三方系統對接時,如果第三方有資料更新,定期進行增量更新就可以了。
而 Redis 主從的部分複制就是指當主從之間的網絡故障等原因造成持續複制中斷了,當從節點再次連上主節點後,主節點就補發資料給從節點,避免了全量複制的過高開銷。補發資料的來源就是複制積壓緩沖的資料。
原理圖如下所示:
部分複制總共分為六步:
(1)當主節點之間失聯後,如果時間超過了 repl-timeout 時間,主節點就認為從節點發生故障了,中斷連接配接。
(2)主節點其實一直都在把用戶端寫指令放入複制積壓緩沖區,是以即使斷連了,主節點還是會保留斷連期間的指令,但因為隊列是固定的,當寫指令太多時,就會導緻部分指令被覆寫了。
(3)主從節點恢複連接配接。
(4)從節點發送 psync 指令給主節點,帶有 runId 和 offset 參數,runId 是上一次複制時儲存的主節點的 runId值,offset 是從節點的複制偏移量。
(5)主節點接收到從節點的指令後,先判斷傳過來的 runId 是否和自己比對,如果不比對,則進行全量複制;如果 runId 比對,則響應 CONTINUE,告訴從節點,可以進行部分複制了。我要把複制積壓緩沖區的資料發給你了哦,請準備好接收。
(6)主節點根據子節點發送的偏移量,将複制積壓緩沖區的資料發送給子節點。
那複制積壓緩沖區到底是怎麼來根據偏移量來計算要發送哪些緩存資料的呢?我們接着往下看。
複制積壓緩沖區
複制積壓緩沖區有幾個特點:
- 固定長度的隊列。
- 最近傳播的寫指令,預設為 1 MB 大小,可調節大小。
- 隊列中的每個位元組都有對應的複制偏移量進行辨別。如下圖所示,每一個位元組對應一個偏移量。
複制積壓緩沖區
從節點重新連上主節點後,會發送 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,來決定如何響應。
原理圖如下所示:
- 全量複制,響應 +FULLRESYNC。
- 部分複制,響應 +CONTINUE。根據從節點發送 runId 是否和主節點相同,offset 偏移量是否在複制積壓緩沖區來判斷是響應全能量複制還是部分複制。
- 使用舊版全量複制,響應 -ERR。說明主伺服器的版本低于 Redis 2.8,識别不了 PSYNC 指令。
複制時如何保持連接配接
說完上面主從節點的連接配接的結構,接下來的問題是這些節點如何在複制時保持連接配接呢?
也就是說主節點和從節點如何知道對方還存活着?其實就是通過心跳檢測,這個在服務注冊和服務發現裡面經常提到。而主節點和從節點都會向對方發送心跳檢測的指令。
主節點發送指令:
主節點會每隔 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?
原因:
- RDB 檔案是二進制檔案,寫入磁盤和網絡傳輸,IO 效率都比記錄和傳輸 AOF 高。
- 從庫端進行恢複時,用 RDB 的恢複效率要高于用 AOF。
主從架構的高可用問題
因為主從架構隻有一個主節點,是以主節點當機後,影響整個系統運作。
如何監控主節點?用 Redis 哨兵機制。
如何增加主節點?用 Redis 叢集。
主從讀寫分離坑不坑?
主從設定為讀寫分離後,需要用戶端綁定主節點進行讀寫,綁定從節點進行讀,這個是比較麻煩的事情。
主從讀寫架構就不能自行判斷往哪個伺服器讀,哪個伺服器寫嗎?其實需要我們自己來控制。
系統維護
主從模式肯定比單機複雜,維護成本也較高。
總結
本篇從宏觀和微觀上講解了主從架構的原理:
微觀視角:涉及了主從架構的三種拓撲結構來應對不同的場景,然後深入講解了持續複制、全量複制、部分複制的差別。以及大家特别關心的複制積壓緩沖區,用戶端緩沖區的使用場景,以及會遇到的坑。
宏觀視角:講解了主從架構誰需要來關心、什麼時候用、以及主從架構會帶來的問題。
後續:主從架構的部署和壓測下篇安排~