數據庫

:分布式Redis深度歷險-復制

摘要

Redis深度歷險分為兩個部分,單機Redis和分布式Redis。本文為分布式Redis深度歷險系列的第一篇,主要內容為Redis的復制功能。Redis的復制功能的作用和大多數分布式存儲系統一樣,就是為了支持主從設計,主從設計的好處有以下幾點:讀寫分離,提高讀寫性能數據備份,減少數據丟失的風險高可用,避免單點故障舊版復制實現Redis的復制主要分為同步和命令傳播兩個步驟:同步可以理解為全量,是將主

Redis深度歷險分為兩個部分,單機Redis和分布式Redis。

本文為分布式Redis深度歷險系列的第一篇,主要內容為Redis的復制功能。

Redis的復制功能的作用和大多數分布式存儲系統一樣,就是為了支持主從設計,主從設計的好處有以下幾點:

  • 讀寫分離,提高讀寫性能
  • 數據備份,減少數據丟失的風險
  • 高可用,避免單點故障
《分布式Redis深度歷險-復制》

舊版復制實現

Redis的復制主要分為同步和命令傳播兩個步驟:

同步可以理解為全量,是將主服務器某一時刻的所有數據全部同步到從服務器。

命令傳播可以理解為增量,當主服務器數據被修改時,主服務器向從服務器發送對應的數據修改命令。

同步

同步分為以下幾個步驟:

1.從服務器向主服務器發送 SYNC 命令(執行 SLAVE OF 命令的第一步也會執行 SYNC 

2.主服務器在收到從服務器命令時,會執行 BGSAVE ,也就是新開一個子進程將內存中的數據保存到RDB文件中。同時使用一個內存緩沖區記錄從現在開始執行的寫命令,該內存緩沖區的作用就是記錄RDB文件生成期間的增量。

3.向從服務器發送RDB文件

4.將緩沖區中的寫命令發送給從服務器

同步可以分為兩種情況,一種是從服務器第一次連接主服務器,另一種是從服務與主服務器的網絡鏈接斷開了,重新連上主服務器并重新同步。

命令傳播

命令傳播實現邏輯比較簡單,當主服務器執行了寫命令后,為了保證從服務器與主服務器數據的一致性,主服務器會將寫命令發送給從服務器,從服務器執行完收到的寫命令后其數據就能和主服務器保持一致了(當然會有延時),注意,從服務器對于客戶端來說是只讀的,因此從服務器的所有數據都是來自于主服務器的同步or命令傳播。

舊版復制存在的問題

假設Redis主從服務器之間的網絡環境不太可靠,我們來看看上述復制方法會出現什么問題。假設有主服務器A和從服務器B,主服務器中目前存在1-10000共一萬條數據。

1.初始連接,從服務器第一次從主服務器同步數據,同步完成后,從服務器也有1-10000共一萬條數據。

2.主服務器新增10001,10002兩條數據

3.通過命令傳播,從服務器也新增10001,10002兩條數據

4.這時候主從服務器之間的網絡斷開

5.主服務器新增數據10003,因為網絡斷開,所以從服務器感受不到數據變化

6.網絡恢復,從服務器重新連接上主服務器,并發送SYNC命令,進行同步操作

7.主服務器將所有數據發送給從服務器(1-10003)

從上述步驟中可以看到,當從服務器重新連接上主服務器時,會重新進行全量同步,造成大量不必要的IO開銷,如果網絡環境不穩定時,會導致主服務器一直將內存中的數據寫到磁盤再發送給從服務器。

新版復制實現

為了解決老版復制問題,Redis2.8對于復制功能進行了優化。實現如下:

1.主服務器會維護一個偏移量,每次向服務器傳播N個字節的數據時,該偏移量就會加上N,比如說一開始是0,接受到一條set key1 value1 后,其偏移量就為13(真實偏移可能不是13,只是舉個例子)。//這里可能要看下代碼確認

2.從服務器也維護一個偏移量,當從服務器收到到主服務器的N個字節數據時,該偏移量會加上N。

3.主服務器維護一個固定大小的緩沖區,每次接受到客戶端寫命令后,都會將對應 命令 往這個緩沖區寫入。當寫入內容超出固定大小后,會覆蓋原來的數據。

4.主服務器有一個唯一id

5.從服務器連接上主服務時,會向主服務器發送上一次連接的主服務器的id以及偏移量,這里又分幾種情況:

  1. 如果從服務器沒傳id或者id與當前主服務器不匹配,那主服務器將傳送全量數據
  2. 如果從服務器的offset在緩沖區中不能找到(落后太多導致緩沖區已經被新數據覆蓋了),那也會進行全量同步
  3. 如果offset能在緩沖區找到,則主服務從offset開始,將緩沖區的數據依次發送給從服務器。(有做pipeline的優化嗎)

以上就是新版復制的大致思路,要注意的是,主服務器緩沖區的大小設置很關鍵,如果設置的太大會導致空間浪費,如果太小會導致網絡環境不好時,其退化為老版復制。

之前我就踩過這樣的坑:在上云時,redis集群在兩個不同機房,主從之前網絡環境不太穩定,而redis機器上存儲的value比較大,很容易就將緩沖區占滿導致每次全量同步,形成惡性循環,從服務器落后不可讀,主服務器不可寫(當從Redis落后太多時,主Redis將拒絕寫入,具體參數可以配置的,下文還會提到)

所以建議將緩沖區大小設置為 平均重連間隔*每秒寫入數據量*2

主從心跳機制

從服務器默認會每秒一次的頻率向主服務器發送心跳: 
REPLCONF A?K <replication_offset> , 
replication_offset代表從服務器當前的復制偏移量。

心跳有三個作用:

1.檢測主從服務器的網絡連接

2.實現min-slaves功能

3.檢測命令丟失

檢測主從服務器的網絡連接

主服務器會記錄從服務器上次發送心跳是什么時間,根據這個時間,我們能知道主從服務器之間的連接是不是出現了故障

實現min-slaves功能

Redis為了保證數據的安全性,可以配置當從服務器小于 min-slaves-to-write 個或者 min-slaves-to-write 個從服務器的延遲都大于等于 min-slaves-max-lag 時,主服務器拒絕寫。

檢測命令丟失

主從之間的復制,其實是以主服務器作為從服務器的客戶端來實現的(在Redis中,所有服務器之間的數據傳遞都是以該種方式)。假設主服務器向從服務器發送一條寫命令,但網絡出現異常,從服務器并沒有收到該命令。


這就會導致數據不一致的狀態(你可能想主服務器發送命令時,如果從沒返回失敗,進行重發不就好了嗎?如果說從成功執行了命令,但是再回復主的時候出現了問題,那主如果重發就會造成數據異常了)。所以主服務器會根據心跳信息來決定要發送的數據??錘隼?

初始,主服務器和從服務器偏移量都是100。

主服務器收到客戶端的寫命令,將偏移量改成110,同時向從服務器發送寫命令,但因網絡原因,從服務器并沒有收到,其偏移量仍然是100。主服務器根據心跳發現從服務器的偏移量是100落后于自己,所以會將100-110的數據進行重發。

看到這里,你可能對于上述方案的正確性感到質疑:在從服務器接收到100-110的數據前,它發送心跳包告訴主服務器自己當前偏移為100,然后接收到了100-110的數據。這時下個心跳還沒發出,主服務器認為從服務器落后于自己,再次發送100-110的數據,導致從服務器再次寫入100-110的數據,導致數據異常!

如果你有想到這個問題,說明你是有在認真思考了~

其實是不存在這種情況的,原因是redis是 單線程 的!記住 單線程 三個字,再回頭看一遍問題描述,相信你能想明白~

原文: Java架構筆記

我還沒有學會寫個人說明!

聯想“融合計算”助力能源行業破局發展

上一篇

MySQL:死鎖一例

下一篇

你也可能喜歡

分布式Redis深度歷險-復制

長按儲存圖像,分享給朋友

ITPUB 每周精要將以郵件的形式發放至您的郵箱


微信掃一掃

微信掃一掃