redis系列--主從複製以及redis複製演進

架構師之路發表於2018-08-05

一、前言

  在之前的文章已經詳細介紹了redis入門基礎已經持久化相關內容包括redis4.0所提供的混合持久化。

  通過持久化功能,Redis保證了即使在伺服器當機情況下資料的丟失非常少。但是如果這臺伺服器出現了硬碟故障、系統崩潰等等,不僅僅是資料丟失,很可能對業務造成災難性打擊。為了避免單點故障通常的做法是將資料複製多個副本儲存在不同的伺服器上,這樣即使有其中一臺伺服器出現故障,其他伺服器依然可以繼續提供服務。當然Redis提供了多種高可用方案包括:主從複製、哨兵模式的主從複製、以及叢集。

  本文將詳細介紹Redis從2.6以到4.0提供複製方案的演進,也包括:主從複製、複製原理以及相關實踐。

歡迎大家加入架構華山論劍:836442475 本群提供免費的學習指導 架構資料 以及免費的解答 不懂得問題都可以在本群提出來 之後還會有職業生涯規劃以及面試指導 進群修改群備註:開發年限-地區-經驗 方便架構師解答問題

二、主從複製

簡介

  在主從複製中,資料庫分為兩類,一類是主庫(master),另一類是同步主庫資料的從庫(slave)。主庫可以進行讀寫操作,當寫操作導致資料變化時會自動同步到從庫。而從庫一般是隻讀的(特定情況也可以寫,通過引數slave-read-only指定),並接受來自主庫的資料,一個主庫可擁有多個從庫,而一個從庫只能有一個主庫。這樣就使得redis的主從架構有了兩種模式:一類是一主多從如下圖1,二類是“鏈式主從複製”--主->從->主-從如下圖2。

對於一主多從的複製架構不必多說,這裡解釋下鏈式主從複製:如上圖2,主庫A的資料會同步到從庫B和從庫C,而B又是從庫D和從庫E的主庫,所以B的資料會同步到從庫D和從庫E。如果向B中寫資料,資料只能同步到D和E中,所以對於這種架構資料的一致性將不能保持,也不推薦這種架構。

搭建配置主從

  由於沒有過多的機器,這裡將使用一臺機器上啟動多個redis例項實現主從複製。

  對於redis來說搭建主從非常容易,引用官網一句話來說:there is a very simple to use and configure leader follower (master-slave) replication。

  本次實踐分別以 10.1.210.68:6379 作為主,兩個從伺服器分別是 10.1.210.69:638010.1.210.69:6381

搭建步驟:

  1. 將redis.conf檔案拷貝三份,名稱最好有顯示的區別,我這裡採用名字為 6379.conf、 6380.conf、 6381.conf;
  2. 分別修改三個檔案的ip(預設127.0.0.1可以不用修改)、埠(儘量和配置檔案一致)、pid檔案,日誌檔案,持久化資料目錄(dir)、後臺執行(daemonize yes);
  3. 使用啟動命令指令碼啟動每個redis服務;
  4. 設定主從關係、驗證主從同步;

示例:

步驟一:

複製程式碼
#建立三個redis目錄
mkdir -p /opt/db/{redis6379,redis6380,redis6381} 

#從原始碼中拷貝配置檔案
cp redis-stable/redis.conf /opt/db/redis6379/6379.conf
cp redis-stable/redis.conf /opt/db/redis6380/6380.conf 
cp redis-stable/redis.conf /opt/db/redis6381/6381.conf 複製程式碼
複製程式碼

步驟二:

修改配置項如下:找到對應的引數修改即可,下面是每個配置檔案修改部分、本機器IP地址是10.1.210.69;

6379.conf
6380.conf
6381.conf

步驟三:

啟動每個redis例項

redis-server /opt/db/redis6379/6379.conf
redis-server /opt/db/redis6380/6380.conf
redis-server /opt/db/redis6381/6381.conf複製程式碼

步驟四:

設定主從關係,當然你可以直接指明從庫配置檔案直接使用slaveof <masterip> <masterport>指定,這裡我在用客戶端修改,分別使用客戶端redis-cli命令連入埠為6380、6381的redis。

連入6380資料庫,使用redis-cli -h 10.1.210.69 -p 6380,其中-h代表ip地址,-p代表埠,並執行slaveof 10.1.210.69 6379,並寫入配置檔案config rewrite,如下:

同樣我們在從庫6381執行相同操作:

此時我們在使用info Replication 檢視相關主從資訊:

同時,還可以測試主從功能,在6379上建立key,在從庫檢視:

主庫:

從庫:

三、複製原理

  瞭解redis複製原理對日後運維有很大幫助,包括如何規劃節點,如何處理節點故障,redis複製過程可分為三個階段:

  1. 複製初始化階段
  2. 資料同步階段
  3. 命令傳播階段

複製初始化階段

  當執行完slaveof masterip port 命令時候,從庫根據指明的master節點ip和port向主庫發起socket連線,主庫收到socket連線之後將連線資訊儲存,此時連線建立;

  當socket連線建立完成以後,從庫向主庫傳送ping命令,以確認主庫是否可用,此時的結果返回如果是PONG則代表主庫可以用,否則可能出現超時或者主庫此時在處理其他任務阻塞那麼此時從庫將斷開socket連線,然後進行重試;

  如果主庫連線設定了密碼,則從庫需要設定masterauth引數,此時從庫會傳送auth命令,命令格式為“auth + 密碼”進行密碼驗證,其中密碼為masterauth引數配置的密碼,需要注意的是如果主庫設定了密碼驗證,從庫未配置masterauth引數則報錯,socket連線斷開。

  當身份驗證完成以後,從節點傳送自己的監聽埠,主庫儲存其埠資訊,此時進入下一個階段:資料同步階段。

資料同步階段

  主庫和從庫都確認對方資訊以後,便可開始資料同步,此時從庫向主庫傳送psync命令(需要注意的是redis4.0版本對2.8版本的psync做了優化,後續會進行說明),主庫收到該命令後判斷是進行增量複製還是全量複製,然後根據策略進行資料的同步,當主庫有新的寫操作時候,此時進入複製第三階段:命令傳播階段。

命令傳播階段

  當資料同步完成以後,在此後的時間裡主從維護著心跳檢查來確認對方是否線上,每隔一段時間(預設10秒,通過repl-ping-slave-period引數指定)主節點向從節點傳送PING命令判斷從節點是否線上,而從節點每秒1次向主節點傳送REPLCONF ACK命令命令格式為:REPLCONF ACK {offset},其中offset指從節點儲存的複製偏移量,作用一是彙報自己複製偏移量,主節點會對比複製偏移量向從節點傳送未同步的命令,作用二在於判斷主節點是否線上,從庫接送命令並執行,最終實現與主庫資料相同。

樂觀複製

  redis採用量樂觀複製策略,容忍在一定時間內主從資料內容是不同的,但是兩者的資料最終會同步。

四、redis複製演進

sync&psync1&psync2

  從redis2.6到4.0開發人員對其複製流程進行逐步的優化,以下是演進過程:

  • redis版本<=2.6<2.8,複製採用sync命令,無論是第一次主從複製還是斷線重連進行復制都採用全量複製;
  • 2.8<=redis版本<4.0,複製採用psync,從redis2.8開始,redis複製從sync過渡到psync,這一特性主要新增了redis在斷線重新時候可使用部分複製;
  • redis版本>=4.0,也採用psync,相比與2.8版本的psync優化了增量複製,這裡我們稱為psync2,2.8版本的psync稱為psync1。

  以下將分別說明各個版本的複製演進。

sync

  在redis2.6以及以前的版本,複製採用sync命令,當一個從庫啟動後,會向主庫傳送sync命令,主庫收到sync命令後執行bgsave後臺儲存RDB快照(該過程在上一篇已經詳細介紹),同時將儲存快照的將快照儲存期間接受的寫命令儲存到緩衝佇列。當快照完成以後,主庫將快照檔案已經快取的所有命令傳送給從庫,從庫接受到快照檔案並載入,再將執行主庫傳送的命令,也就是上面我們介紹的複製初始化階段和資料同步階段,其後就是命令增量同步,最終主庫與從庫保持資料一直。

  當從庫在某些情況斷線重連(如從庫重啟、由於網路原因主從連線超時),重複上述過程,進行資料同步。由此可見,redis2.6版本以及2.6以前複製過程全部採用全量複製。

  sync雖然解決了資料同步問題,但是在資料量比較大情況下,從庫斷線從來依然採用全量複製機制,無論是從資料恢復、寬頻佔用來說,sync所帶來的問題還是很多的。於是redis從2.8開始,引入新的命令psync。

psync1

  在redis2.8版本,redis引入psync命令來進行主從的資料同步,這裡我們稱該命令為psync1。psync1實現依賴以下三個關鍵點:

  1.offset(複製偏移量):

  主庫和從庫分別各自維護一個複製偏移量(可以使用info replication檢視),用於標識自己複製的情況,在主庫中代表主節點向從節點傳遞的位元組數,在從庫中代表從庫同步的位元組數。每當主庫向從節點傳送N個位元組資料時,主節點的offset增加N,從庫每收到主節點傳來的N個位元組資料時,從庫的offset增加N。因此offset總是不斷增大,這也是判斷主從資料是否同步的標誌,若主從的offset相同則表示資料同步量,不通則表示資料不同步。以下圖示分別代表某個時刻兩個主從的同步情況(以下是4.0版本截圖):

  

  2.replication backlog buffer(複製積壓緩衝區):

  複製積壓緩衝區是一個固定長度的FIFO佇列,大小由配置引數repl-backlog-size指定,預設大小1MB。需要注意的是該緩衝區由master維護並且有且只有一個,所有slave共享此緩衝區,其作用在於備份最近主庫傳送給從庫的資料。

  在主從命令傳播階段,主節點除了將寫命令傳送給從節點外,還會傳送一份到複製積壓緩衝區,作為寫命令的備份。除了儲存最近的寫命令,複製積壓緩衝區中還儲存了每個位元組相應的複製偏移量(如下圖),由於複製積壓緩衝區固定大小先進先出的佇列,所以它總是儲存的是最近redis執行的命令。

  3.run_id(伺服器執行的唯一ID)

  每個redis例項在啟動時候,都會隨機生成一個長度為40的唯一字串來標識當前執行的redis節點,檢視此id可通過命令info server檢視。

  當主從複製在初次複製時,主節點將自己的runid傳送給從節點,從節點將這個runid儲存起來,當斷線重連時,從節點會將這個runid傳送給主節點。主節點根據runid判斷能否進行部分複製:

  • 如果從節點儲存的runid與主節點現在的runid相同,說明主從節點之前同步過,主節點會更具offset偏移量之後的資料判斷是否執行部分複製,如果offset偏移量之後的資料仍然都在複製積壓緩衝區裡,則執行部分複製,否則執行全量複製;
  • 如果從節點儲存的runid與主節點現在的runid不同,說明從節點在斷線前同步的redis節點並不是當前的主節點,只能進行全量複製;

  介紹完三個概念以後,接下來就可以介紹redis2.8提供的psync命令實現過程,如下圖:

  圖文說明:

  • 如果從伺服器以前沒有複製過任何主伺服器,或者之前執行過SLAVEOF no one命令,那麼從伺服器在開始一次新的複製時將向主伺服器傳送PSYNC ? -1命令,主動請求主伺服器進行完整重同步(因為這時不可能執行部分重同步);
  • 相反地,如果從伺服器已經複製過某個主伺服器,那麼從伺服器在開始一次新的複製時將向主伺服器傳送PSYNC <runid> <offset>命令:其中runid是上一次複製的主伺服器的執行ID,而offset則是從伺服器當前的複製偏移量,接收到這個命令的主伺服器會通過這兩個引數來判斷應該對從伺服器執行哪種同步操作,如何判斷已經在介紹runid時進行詳細說明。

根據情況,接收到PSYNC命令的主伺服器會向從伺服器返回以下三種回覆的其中一種:

  • 如果主伺服器返回+FULLRESYNC <runid> <offset>回覆,那麼表示主伺服器將與從伺服器執行完整重同步操作:其中runid是這個主伺服器的執行ID,從伺服器會將這個ID儲存起來,在下一次傳送PSYNC命令時使用;而offset則是主伺服器當前的複製偏移量,從伺服器會將這個值作為自己的初始化偏移量;
  • 如果主伺服器返回+CONTINUE回覆,那麼表示主伺服器將與從伺服器執行部分同步操作,從伺服器只要等著主伺服器將自己缺少的那部分資料傳送過來就可以了;
  • 如果主伺服器返回-ERR回覆,那麼表示主伺服器的版本低於Redis 2.8,它識別不了PSYNC命令,從伺服器將向主伺服器傳送SYNC命令,並與主伺服器執行完整同步操作。

  由此可見psync也有不足之處,當從庫重啟以後runid發生變化,也就意味者從庫還是會進行全量複製,而在實際的生產中進行從庫的維護很多時候會進行重啟,而正是有由於全量同步需要主庫執行快照,以及資料傳輸會帶不小的影響。因此在4.0版本,psync命令做了改進,以下說明。

psync2

  redis4.0新版本除了增加混合持久化,還優化了psync(以下稱psync2)並實現即使redis例項重啟的情況下也能實現部分同步,下面主要介紹psync2實現過程。psync2在psync1基礎上新增兩個複製id(可使用info replication 檢視如下圖):

  • master_replid: 複製id1(後文簡稱:replid1),一個長度為41個位元組(40個隨機串+’0’)的字串,每個redis例項都有,和runid沒有直接關聯,但和runid生成規則相同。當例項變為從例項後,自己的replid1會被主例項的replid1覆蓋。

  • master_replid2:複製id2(後文簡稱:replid2),預設初始化為全0,用於儲存上次主例項的replid1。

  在4.0之前的版本,redis複製資訊完全丟失,所以每個例項重啟後只能進行全量複製,到了4.0版本,任然可以使用部分同步,其實現過程:

第一步:儲存複製資訊

  redis在關閉時,通過shutdown save,都會呼叫rdbSaveInfoAuxFields函式,把當前例項的repl-id和repl-offset儲存到RDB檔案中,當前的RDB儲存的資料內容和複製資訊是一致性的可通過redis-check-rdb命令檢視。

第二步:重啟後載入RDB檔案中的複製資訊

  redis載入RDB檔案,會專門處理檔案中輔助欄位(AUX fields)資訊,把其中repl_id和repl_offset載入到例項中,分別賦給master_replid和master_repl_offset兩個變數值,特別注意當從庫開啟了AOF持久化,redis載入順序發生變化優先載入AOF檔案,但是由於aof檔案中沒有複製資訊,所以導致重啟後從例項依舊使用全量複製!

第三步:向主庫上報復制資訊,判斷是否進行部分同步

  從例項向主庫上報master_replid和master_repl_offset+1;從例項同時滿足以下兩條件,就可以部分重新同步,否則執行全量同步:

  • 從例項上報master_replid串,與主例項的master_replid1或replid2有一個相等,用於判斷主從未發生改變;
  • 從例項上報的master_repl_offset+1位元組,還存在於主例項的複製積壓緩衝區中,用於判斷從庫丟失部分是否在複製緩衝區中;

psync2除了解決redis重啟使用部分同步外,還為解決在主庫故障時候從庫切換為主庫時候使用部分同步機制。redis從庫預設開啟複製積壓緩衝區功能,以便從庫故障切換變化master後,其他落後該從庫可以從緩衝區中獲取缺少的命令。該過程的實現通過兩組replid、offset替換原來的master runid和offset變數實現:

  • 第一組:master_replid和master_repl_offset:如果redis是主例項,則表示為自己的replid和複製偏移量; 如果redis是從例項,則表示為自己主例項的replid1和同步主例項的複製偏移量。
  • 第二組:master_replid2和second_repl_offset:無論主從,都表示自己上次主例項repid1和複製偏移量;用於兄弟例項或級聯複製,主庫故障切換psync。

判斷是否使用部分複製條件:如果從庫提供的master_replid與master的replid不同,且與master的replid2不同,或同步速度快於master; 就必須進行全量複製,否則執行部分複製。

以下常見的主從切換都可以使用部分複製:

  1. 一主一從發生切換,A->B 切換變成 B->A ;
  2. 一主多從發生切換,兄弟節點變成父子節點時;
  3. 級別複製發生切換, A->B->C 切換變成 B->C->A;

用一句redis開發者話來說psync2,儘管它不是非常完美,但是已經非常適用。

五、馬上實踐

  為了演示4.0的psync2功能,這裡做實踐演示。主從例項採用上述搭建的主從架構,主庫:10.1.210.69:6379 、從庫:10.1.210.69:6380和10.1.210.69:6381。首先關閉一臺從節點10.1.210.69:6380:

檢視日誌從庫執行了RDB快照:

檢視RDB檔案,裡面記錄了相關複製資訊:

此時我們在啟動從庫,檢視對應日誌,此時啟用部分複製來恢復資料:

之前提到過,當從庫開啟來AOF持久化時候,重啟時載入檔案從AOF檔案中載入,但是AOF檔案中沒有對應的複製資訊,所以從例項依舊採用全量複製。以下是從庫開啟AOF持久化後,重啟日誌,可以看到是全量同步:

六、總結

複製演進中解決的問題

  早起版本才用的sync同步方法,雖然實現了簡單的主從同步,但是在從庫斷線或重啟時無法實現部分同步,由此在2.8版本推出psync1,redis2.8的部分同步機制,有效解決了網路環境不穩定、redis執行高時間複雜度的命令引起的複製中斷,從而導致全量同步。但在應對從庫重啟和主庫故障切換的場景時,psync1還是需進行全量同步。於是在4.0新的psync得到了加強,redis4.0通過在關閉時候執行RDB快照,將複製資訊儲存在RDB中等到重新啟動時載入RDB檔案載入複製資訊,通過對比複製資訊啟用部分複製,有效的解決了psync1情形下從庫重啟複製資訊丟失而導致全量同步的問題。同時引入兩組replid、offset,主從切換時交換兩組值來實現主從故障切換時候依舊採用部分複製。

  再次強調,在上述的過程的實現是從庫不開啟AOF持久化情況下,如果從庫開啟的AOF持久化,重啟時候依然使用全量複製。

故障切換 

  在實際生產環境中,在沒有哨兵的主從架構裡如果要重啟從庫,比較好的方式是先動態調配主庫中的複製積壓緩衝佇列,調整大小應大於某個N值,N值計算公式:backlog_size = 重啟從例項時長 * 主例項offset每秒寫入量,這樣好處在於,即使從庫重啟斷線一段時間後再啟動任然保持部分複製。調整方式通過config set repl-backlog-size <位元組數>,當我們重啟完成後又可以將

repl-backlog-size重新調回原有值。當然在資料量小或者重啟時間短情況下,也可以直接重啟從節點。 

  當主庫當機時候,在沒有哨兵情況下,需要現將從節點中的某一臺提升為主庫,我們需要在所有從節點中找到當前的offset最大值的從庫(這樣代表該庫相對其他從庫資料較全),然後執行slaveof no one將該從庫提升為主庫,最後將所有其他重庫依次執行slaveof ip port(ip和port是新主庫的IP地址和埠),最後完成故障切換。此外,redis4.0中這種切換任然採用部分複製進行資料同步。

主從配置引數

複製程式碼
########從庫##############

slaveof <masterip> <masterport> 
#設定該資料庫為其他資料庫的從資料庫

masterauth <master-password>
#主從複製中,設定連線master伺服器的密碼(前提master啟用了認證)

slave-serve-stale-data yes
# 當從庫同主庫失去連線或者複製正在進行,從庫有兩種執行方式:
# 1) 如果slave-serve-stale-data設定為yes(預設設定),從庫會繼續相應客戶端的請求
# 2) 如果slave-serve-stale-data設定為no,除了INFO和SLAVOF命令之外的任何請求都會返回一個錯誤"SYNC with master in progress"

slave-priority 100
#當主庫發生當機時候,哨兵會選擇優先順序最高的一個稱為主庫,從庫優先順序配置預設100,數值越小優先順序越高

slave-read-only yes
#從節點是否只讀;預設yes只讀,為了保持資料一致性,應保持預設


####主庫##############

repl-disable-tcp-nodelay no
#在slave和master同步後(傳送psync/sync),後續的同步是否設定成TCP_NODELAY假如設定成yes,則redis會合並小的TCP包從而節省頻寬,但會增加同步延遲(40ms),造成master與slave資料不一致假如設定成no,則redis master會立即傳送同步資料,沒有延遲
#前者關注效能,後者關注一致性

repl-ping-slave-period 10
#從庫會按照一個時間間隔向主庫傳送PING命令來判斷主伺服器是否線上,預設是10秒

repl-backlog-size 1mb
#複製積壓緩衝區大小設定

repl-backlog-ttl 3600
#master沒有slave一段時間會釋放複製緩衝區的記憶體,repl-backlog-ttl用來設定該時間長度。單位為秒。

min-slaves-to-write 3
min-slaves-max-lag 10
#設定某個時間斷內,如果從庫數量小於該某個值則不允許主機進行寫操作,以上參數列示10秒內如果主庫的從節點小於3個,則主庫不接受寫請求,min-slaves-to-write 0代表關閉此功能。複製程式碼
複製程式碼


相關文章