分佈式Redis深度歷險-複製

Redis深度歷險分爲兩個部分,單機Redis和分佈式Redis。java

本文爲分佈式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架構筆記

免費Java高級資料須要本身領取,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高併發分佈式等教程,一共30G。           
傳送門:            https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q
相關文章
相關標籤/搜索