深入理解redis主從複製原理

引言

我們都知道redis性能很高,單機版的qps可以達到10萬+,但是即使如此我們也不會在生產環境去搭建單機版本的redis,原因如下:

1、因為存在單點,如果進程掛掉或者機器宕機,redis不可用

2、redis的讀和寫全在一臺機器上,無法滿足讀多寫少的場景。

主從流程架構

Redis雖然讀取寫入的速度都特別快,但是也會產生讀壓力特別大的情況。為了分擔讀壓力,Redis支持主從複製,Redis的主從結構可以採用一主多從或者級聯結構,下圖為級聯結構。

深入理解redis主從複製原理

1.搭建主從架構

接下來教大家如何搭建主從架構的redis,我們可以根據不同的端口號模擬在單機啟動3個redis進程,端口號6379的為master,端口號6370 和6371的為slave。

深入理解redis主從複製原理

我們首先複製redis.conf文件2份,分別為redis_6370.conf 和redis_6371.conf ,然後分別對這兩個文件修改如下參數:

1slaveof 127.0.0.1 6379

啟動master:

1./redis-server redis.conf

啟動slave1:

1./redis-server redis_6370.conf

啟動slave2:

1./redis-server redis_6371.conf

接下來我們在master上設置一個值,如下圖:

深入理解redis主從複製原理

我們看下slave1 和slave2 上是否能取到該值,如下圖:

深入理解redis主從複製原理

深入理解redis主從複製原理

可以看到此時slave1和slave2已經有相應的值,說明同步完成。

2. Redis各配置參數介紹:

  1. slaveof
  2. 設置從節點指向要複製的主節點
  3. masterauth
  4. 複製使用的密碼
  5. slave-serve-stale-data yes
  6. 當slave丟失與master的連接時,或者slave仍然在於master進行數據同步時(還沒有與master保持一致)
  7. slave可以有兩種方式來響應客戶端請求:
  8. 1) 如果 slave-serve-stale-data 設置成 'yes' (the default) slave會仍然響應客戶端請求,此時可能會有問題。
  9. 2) 如果 slave-serve-stale data設置成 'no' slave會返回"SYNC with master in progress"這樣的錯誤信息。 但 INFO 和SLAVEOF命令除外。
  10. slave-read-only yes
  11. 從節點是否只讀。
  12. repl-diskless-sync no (no, Disk-backed, Diskless )
  13. 複製集同步策略:磁盤或者socket
  14. no: 表示不啟用
  15. Disk-backed, 基於磁盤的
  16. Diskless: 無磁盤的
  17. 新slave連接或者老slave重新連接時候不能只接收不同,得做一個全同步。需要一個新的RDB文件dump出來,然後從master傳到slave。
  18. 可以有兩種情況:
  19. 1、基於硬盤(disk-backed):master創建一個新進程dump RDB,完事之後由父進程(即主進程)增量傳給slaves。
  20. 2、基於socket(diskless):master創建一個新進程直接dump RDB到slave的socket,不經過主進程,不經過硬盤。
  21. 基於硬盤的話,RDB文件創建後,一旦創建完畢,可以同時服務更多的slave就是一對多。基於socket的話, 新slave來了後,得排隊(如果超出了repl-diskless-sync-delay還沒來),完事兒一個再進行下一個。
  22. 當用diskless的時候,master等待一個repl-diskless-sync-delay的秒數,如果沒slave來的話,就直接傳,後來的得排隊等了。否則就可以一起傳。
  23. disk較慢,並且網絡較快的時候,可以用diskless。(默認用disk-based)
  24. repl-diskless-sync-delay 5
  25. 如果啟用無磁盤的同步,在接入進來延遲5秒以後在同步數據
  26. repl-ping-slave-period 10
  27. 主節點每隔10秒探測一次從節點是否存活。
  28. repl-timeout 60
  29. 主從節點如果網絡斷開超時60秒未通信,就超時斷開連接
  30. repl-disable-tcp-nodelay no
  31. 是否啟用tcp的延遲發送, 就是說數據量比TCP的首部信息加起來還要小,這樣反覆的發送較小的數據很佔用資源,當開啟以後就會累積到一定的數據量在發送。但是對於較高的場景,如果延遲發送的時間過長,可能主持同步時間就會延遲。
  32. repl-backlog-size 1mb
  33. 如果主Redis等了一段時間之後,還是無法連接到從Redis,那麼緩衝隊列中的數據將被清理掉。我們可以設置主Redis要等待的時間長度。如果設置為0,則表示永遠不清理。默認是1個小時。
  34. slave-priority 100
  35. 複製集群中,主節點故障時,sentinel應用場景中的主節點選舉時使用的優先級;數字越小優先級越高,但0表示不參與選舉;
  36. min-slaves-to-write 3
  37. 主節點僅允許其能夠通信的從節點數量大於等於此處的值時接受寫操作;意思就是說,我們的redis集群最少要有3臺以上才能滿足我們線上的業務,如果低於這個數,將無法啟用.
  38. min-slaves-max-lag 10
  39. 從節點延遲時長滯後超出此處指定的時長時,主節點會拒絕寫入操作;

主從底層實現原理

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

  1. 同步操作用於將從服務器的數據庫狀態更新至主服務器當前所處的數據庫狀態;
  2. 命令傳播操作則用於在主服務器的數據庫狀態被修改,導致主從服務器的數據庫狀態出現不一致時,讓主從服務器的數據庫重新回到一致狀態。

以下為redis2.8之前版本的同步實現原理:

1. 同步

當客戶端向從服務器發送SLAVEOF命令,要求從服務器複製主服務器時,從服務器首先需要執行同步操作,也即是,將從服務器的數據庫狀態更新至主服務器當前所處的數據庫狀態。

從服務器對主服務器的同步操作需要通過向主服務器發送SYNC命令來完成,以下是SYNC命令的執行步驟:

  1. 從服務器向主服務器發送SYNC命令;
  2. 收到SYNC命令的主服務器執行BGSAVE命令,在後臺生成一個RDB文件,並使用一個緩衝區記錄從現在開始執行的所有寫命令;
  3. 當主服務器的BGSAVE命令執行完畢時,主服務器會將BGSAVE命令生成的RDB文件發送給從服務器,從服務器接收並載入這個RDB文件,將自己的數據庫狀態更新至主服務器執行BGSAVE命令時的數據庫狀態。
  4. 主服務器將記錄在緩衝區裡面的所有寫命令發送給從服務器,從服務器執行這些寫命令,將自己的數據庫狀態更新至主服務器數據庫當前所處的狀態。

2. 命令傳播

在執行完同步操作之後,主從服務器之間數據庫狀態已經相同了。但這個狀態並非一成不變,如果主服務器執行了寫操作,那麼主服務器的數據庫狀態就會修改,並導致主從服務器狀態不再一致。

所以為了讓主從服務器再次回到一致狀態,主服務器需要對從服務器執行命令傳播操作:主服務器會將自己執行的寫命令,也即是造成主從服務器不一致的那條寫命令,發送給從服務器執行,當從服務器執行了相同的寫命令之後,主從服務器將再次回到一致狀態。

3. redis2.8之前版本複製的缺陷

在Redis中,從服務器對主服務器的複製可以分為以下兩種情況:

  1. 初次複製:從服務器以前沒有複製過任何主服務器,或者從服務器當前要複製的主服務器和上一次複製的主服務器不同;
  2. 斷線後重複製:處於命令傳播階段的主從服務器因為網絡原因而中斷了複製,但從服務器通過自動重連接重新連上了主服務器,並繼續複製主服務器。

對於初次複製來說,舊版複製功能能夠很好地完成任務,但對於斷線後重複製來說,舊版複製功能雖然也能讓主從服務器重新回到一致狀態,但效率卻非常低。

SYNC命令是非常消耗資源的,因為每次執行SYNC命令,主從服務器需要執行一下操作:

  1. 主服務器需要執行BGSAVE命令來生成RDB文件,這個生成操作會耗費主服務器大量的CPU、內存和磁盤I/O資源;
  2. 主服務器需要將自己生成的RDB文件發送給從服務器,這個發送操作會耗費主從服務器大量的網絡資源(帶寬和流量),並對主服務器響應命令請求的時間產生影響;
  3. 接收到RDB文件的從服務器需要載入主服務器發來的RDB文件,並且在載入期間,從服務器會因為阻塞而沒辦法處理命令請求。

SYNC是一個如此消耗資源的命令,所以Redis最好在真需要的時候才需要執行SYNC命令。

新版複製功能的實現

為了解決舊版複製功能在處理斷線重複制情況時的低效問題,Redis從2.8版本開始,使用PSYNC命令代替SYNC命令來執行復制時的同步操作。

PSYNC命令具有完整重同步(full resynchronization)和部分重同步(partial resynchronization)兩種模式:

  1. 其中完整重同步用於處理初次複製情況:完整重同步的執行步驟和SYNC命令的執行步驟基本一樣,它們都是通過讓主服務器創建併發送RDB文件,以及向從服務器發送保存在緩衝區裡面的寫命令來進行同步;
  2. 而部分重同步則用於處理斷線後重複製情況:當從服務器在斷線後重新連接主服務器時,如果條件允許,主服務器可以將主從服務器連接斷開期間執行的寫命令發送給從服務器,從服務器只要接收並執行這些寫命令,就可以將數據庫更新至主服務器當前所處的狀態。

部分重同步實現

部分重同步功能由以下三個部分構成:

  1. 主服務器的複製偏移量(replication offset)和從服務器的複製偏移量;
  2. 主服務器的複製積壓緩衝區(replication backlog);
  3. 服務器的運行ID(run ID)

複製偏移量

執行復制的雙方——主服務器和從服務器會分別維護一個複製偏移量:

  1. 主服務器每次向從服務器傳播N個字節的數據時,就將自己的複製偏移量的值加上N;
  2. 從服務器每次收到主服務器傳播來的N個字節的數據時,就將自己的複製偏移量的值加上N;

通過對比主從服務器的複製偏移量,程序可以很容易地知道主從服務器是否處於一致狀態:

  1. 如果主從服務器處於一致狀態,那麼主從服務器兩者的偏移量總是相同的;
  2. 相反,如果主從服務器兩者的偏移量並不相同,那麼說明主從服務器並未處於一致狀態。
深入理解redis主從複製原理

假設Slave A在短線之後立即重新連接master,並且成功,接下來slave A向master 發送PSYNC命令,master對比slave A的偏移量發現不相等,那麼這時候是使用完全同步還是部分同步呢?如果部分同步的話,master如何補償slave A在短線期間的數據呢?這就和下面所說的複製積壓緩衝區有關。

複製積壓緩衝區

複製積壓緩衝區是由主服務器維護的一個固定長度(fixed-size)先進先出(FIFO)隊列,默認大小為1MB。

和普通先進先出隊列隨著元素的增加和減少而動態調整長度不同,固定長度先進先出隊列的長度是固定的,當入隊元素的數量大於隊列長度時,最先入隊的元素會被彈出,而新元素會被放入隊列。

當主服務器進行命令傳播時,它不僅會將寫命令發送給所有從服務器,還會將寫命令入隊到複製積壓緩衝區裡面,如圖所示:

深入理解redis主從複製原理

因此,主服務器的複製積壓緩衝區裡面會保存著一部分最近傳播的寫命令,並且複製積壓緩衝區會為隊列中的每個字節記錄相應的複製偏移量,就像下表所示的那樣。

深入理解redis主從複製原理

當從服務器重新連上主服務器時,從服務器會通過PSYNC命令將自己的複製偏移量offset發送給主服務器,主服務器會根據這個複製偏移量來決定對從服務器執行何種同步操作:

  1. 如果offset偏移量之後的數據仍然存在於複製積壓緩衝區裡面,那麼主服務器將對從服務器執行部分重同步操作;
  2. 相反,如果offset偏移量之後的數據已經不存在於複製積壓緩衝區,那麼主服務器將對從服務器執行完整重同步操作。

複製積壓緩衝區的大小

Redis為複製積壓緩衝區設置的默認大小為1MB,如果主服務器需要執行大量寫命令,又或者主從服務器斷線後重連接所需的時間比較長,那麼這個大小也許並不合適。如果複製積壓緩衝區的大小設置得不恰當,那麼PSYNC命令的複製重同步模式就不能正常發揮作用,因此,正確估算和設置複製積壓緩衝區的大小非常重要。

複製積壓緩衝區的最小大小可以根據公式second*write_size_per_second來估算:

  1. 其中second為從服務器斷線後重新連接上主服務器所需的平均時間(以秒計算);
  2. 而write_size_per_second則是主服務器平均每秒產生的寫命令數據量(協議格式的寫命令的長度總和);

例如,如果主服務器平均每秒產生1 MB的寫數據,而從服務器斷線之後平均要3秒才能重新連接上主服務器,那麼複製積壓緩衝區的大小就不能低於3MB。

為了安全起見,可以將複製積壓緩衝區的大小設2secondwrite_size_per_second,這樣可以保證絕大部分斷線情況都能用部分重同步來處理。至於複製積壓緩衝區大小的修改方法,可以參考配置文件中關於repl-backlog-size選項的說明。

服務器運行ID

除了複製偏移量和複製積壓緩衝區之外,實現部分重同步還需要用到服務器運行ID(run ID):

  1. 每個Redis服務器,不論主服務器還是從服務,都會有自己的運行ID;
  2. 運行ID在服務器啟動時自動生成,由40個隨機的十六進制字符組成,例如53b9b28df8042fdc9ab5e3fcbbbabff1d5dce2b3;

當從服務器對主服務器進行初次複製時,主服務器會將自己的運行ID傳送給從服務器,而從服務器則會將這個運行ID保存起來.

當從服務器斷線並重新連上一個主服務器時,從服務器將向當前連接的主服務器發送之前保存的運行ID:

  1. 如果從服務器保存的運行ID和當前連接的主服務器的運行ID相同,那麼說明從服務器斷線之前複製的就是當前連接的這個主服務器,主服務器可以繼續嘗試執行部分重同步操作;
  2. 相反地,如果從服務器保存的運行ID和當前連接的主服務器的運行ID並不相同,那麼說明從服務器斷線之前複製的主服務器並不是當前連接的這個主服務器,主服務器將對從服務器執行完整重同步操作。

PSYNC命令的實現

PSYNC命令的調用方法有兩種:

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

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

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

sentinel 哨兵模式

Redis-Sentinel是Redis官方推薦的高可用性(HA)解決方案,當用Redis做Master-slave的高可用方案時,假如master宕機了,Redis本身(包括它的很多客戶端)都沒有實現自動進行主備切換,而Redis-sentinel本身也是一個獨立運行的進程,它能監控多個master-slave集群,發現master宕機後能進行自動切換。

正是由於redis的主從複製不能更好實現高可用,所以才有了sentinel模式,Sentinel原理也很簡單:當主節點出現故障時,由Redis Sentinel自動完成故障發現和轉移,並通知應用方,實現高可用性。如下圖所示:

深入理解redis主從複製原理

其實整個過程只需要一個哨兵節點來完成,首先使用Raft算法(選舉算法)實現選舉機制,選出一個哨兵節點來完成轉移和通知

哨兵的定時監控任務

  1. 每個哨兵節點每10秒會向主節點和從節點發送info命令獲取最拓撲結構圖,哨兵配置時只要配置對主節點的監控即可,通過向主節點發送info,獲取從節點的信息,並當有新的從節點加入時可以馬上感知到
深入理解redis主從複製原理

每個哨兵節點每隔2秒會向redis數據節點的指定頻道上發送該哨兵節點對於主節點的判斷以及當前哨兵節點的信息,同時每個哨兵節點也會訂閱該頻道,來了解其它哨兵節點的信息及對主節點的判斷,其實就是通過消息publish和subscribe來完成的

深入理解redis主從複製原理

每隔1秒每個哨兵會向主節點、從節點及其餘哨兵節點發送一次ping命令做一次心跳檢測,這個也是哨兵用來判斷節點是否正常的重要依據

深入理解redis主從複製原理

  1. sentinel對於不可用有兩種不同的看法,一個叫主觀不可用(SDOWN),另外一個叫客觀不可用(ODOWN)。SDOWN是sentinel自己主觀上檢測到的關於master的狀態,ODOWN需要一定數量的sentinel達成一致意見才能認為一個master客觀上已經宕掉,各個sentinel之間通過命令SENTINEL is_master_down_by_addr來獲得其它sentinel對master的檢測結果。
  2. 當主觀下線的節點是主節點時,此時該哨兵B節點會通過指令sentinel is-masterdown-by-addr尋求其它哨兵節點對主節點的判斷,當超過quorum(選舉)個數,此時哨兵節點則認為該主節點確實有問題,這樣就客觀下線了,大部分哨兵節點都同意下線操作,也就說是客觀下線
深入理解redis主從複製原理

哨兵選舉流程

  1. 每個在線的哨兵節點都可以成為領導者,當它確認(比如哨兵B)主節點下線時,會向其它哨兵發is-master-down-by-addr命令,徵求判斷並要求將自己設置為領導者,由領導者處理故障轉移;
  2. 當其它哨兵收到此命令時,可以同意或者拒絕它成為領導者
  3. 如果哨兵B發現自己在選舉的票數大於等於num(sentinels)/2+1時,將成為領導者,如果沒有超過,繼續選舉
深入理解redis主從複製原理

故障轉移機制

  1. 由Sentinel節點定期監控發現主節點是否出現了故障,
  2. sentinel會向master發送心跳PING來確認master是否存活,如果master在“一定時間範圍”內不回應PONG 或者是回覆了一個錯誤消息,那麼這個sentinel會主觀地(單方面地)認為這個master已經不可用了.
  3. 當主節點出現故障,此時Sentine集群中的節點共同選舉了Sentinel B節點為領導,負載處理主節點的故障轉移。
  4. 由Sentinel B領導節點執行故障轉移,過程和主從複製一樣,但是自動執行
深入理解redis主從複製原理

總結下流程:

  1. 將slave A脫離原從節點,升級主節點,
  2. 將從節點slave B指向新的主節點
  3. 將原主節點(oldMaster)變成從節點,指向新的主節點
  4. 通知客戶端主節點已更換

安裝和部署哨兵

以3個Sentinel節點、2個從節點、1個主節點為例進行安裝部署

1,先搭好一主兩從redis的主從複製,和之前的主從複製搭建一樣,這裡不在重複。

2,redis sentinel哨兵機制核心配置

1) 進入到redis的安裝目錄,找到sentinel.conf文件,複製2份:

sentinel.conf

sentinel_26380.conf

sentinel_26381.conf

2) 將該三個文件的端口號(port)分別修改成 26379,26380,26381;

3)找到 sentinel monitor

修改成 sentinel monitor myname 127.0.0.1 6379 2

(127.0.0.1 是master的ip,6379是master的端口號,2代表當集群中有2個sentinel認為master死了時,才能真正認為該master已經不可用了)

哨兵其它的配置:

  1. sentinel auth-pass mymaster 12345678
  2. sentinel連主節點的密碼
  3. sentinel config-epoch mymaster 2
  4. 故障轉移時最多可以有2從節點同時對新主節點進行數據同步
  5. sentinel leader-epoch mymaster 2
  6. 同時一時間最多2個slave可同時更新配置,建議數字不要太大,以免影響正常對外提供服務
  7. sentinel failover-timeout mymasterA 180000
  8. 故障轉移超時時間180s,
  9. 1),如果轉移超時失敗,下次轉移時時間為之前的2倍;
  10. 2),從節點變主節點時,從節點執行slaveof no one命令一直失敗的話,當時間超過180S時,則故障轉移失敗
  11. 3),從節點複製新主節點時間超過180S轉移失敗
  12. sentinel down-after-milliseconds mymasterA 300000
  13. sentinel節點定期向主節點ping命令,當超過了300S時間後沒有回覆,可能就認定為此主節點出現故障了.
  14. sentinel parallel-syncs mymasterA 1
  15. 故障轉移後,1代表每個從節點按順序排隊一個一個複製主節點數據,如果為3,指3個從節點同時併發複製主節點數據,不會影響阻塞,但存在網絡和IO開銷

3,啟動redis服務和sentinel服務:

為了方便觀察日誌,本次服務啟動並沒有後臺運行,實際生產環境建議在後臺啟動(也就是在命令的末尾加上 & )

1./redis-server redis.conf

2./redis-server redis_6370.conf

3./redis-server redis_6380.conf

啟動sentinel服務:

1./redis-sentinel sentinel.conf

2./redis-sentinel sentinel_26380.conf

3./redis-sentinel sentinel_26381.conf

到此服務全部啟動完畢.

4,測試,殺死6379的redis服務

殺死6379的redis服務後,6370變成了master了

深入理解redis主從複製原理

深入理解redis主從複製原理

5, 重新啟動6379以後變為6370的從節點

深入理解redis主從複製原理

深入理解redis主從複製原理

6,部署建議

  1. 生產環境sentinel節點應部署在多臺物理機
  2. 至少三個且奇數個sentinel節點


分享到:


相關文章: