Redis簡介二(一篇讀懂主從複製)


在上一篇我們簡單介紹了下Redis的使用,下面介紹集羣環境下的Redis。

集羣的意義

Redis集羣的出現勢必是爲了解決單機不可解決的問題,有哪些問題呢,簡單總結如下:

  • 在單機環境下,如果某一個Redis實例不可用,或者所有實例不可用,Redis無論是作爲緩存還是作爲數據庫使用,都必然對業務造成很大影響,單點機器出現故障的概率還是很高的。
  • Redis作爲一個純內存操作的應用,受OS限制,內存不可能無限擴大,如果我們需要同時緩存幾十上百G的數據,單機環境無法提供支持
  • 單機在機器性能非常好的情況下,QPS官方是在10-15w,但是考慮到有很多子線程任務,一般應該會低於10w,單機無法支持高併發環境。

CAP原則

也稱CAP定理,即在一個分佈式系統中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分區容錯性),三者不可得兼。舉個例子,強一致性與可用性是一對冤家,無法共存,下面會有介紹。Redis集羣實現的是AP。

集羣實現方案

1.解決單點故障問題

既然一臺機器不可靠,那就多搞幾臺嘛。水平擴展Redis服務器不是就解決了問題嘛,同時還能順帶着解決下性能問題:在這裏插入圖片描述
一臺Master外掛n臺salve,每個salve理論上持有Master的全量數據,相當於Master有n個鏡像,當Master down了之後,slave仍舊可以持有完整數據對外提供服務,還能支持讀寫分離,想想還有點小激動呢。

首先解釋兩個名詞:

  • 主從
    簡單理解就是,client即可以與主機連接,還可以和從機連接
  • 主備
    備,即備份機,client是無法與備機進行連接,只有主機down了之後,備機替代主機功能纔可以連接

一般主機可以讀寫,從機只能讀。

附帶出現的新問題

一致性問題

先冷靜一下,該如何讓每個slave持有完整的數據呢?數據一致性的問題就這麼出現了,下面換個簡化圖示例:
在這裏插入圖片描述
client向Matser寫入一條數據,然後Master向兩臺slave同步數據。假設現在slave1接收到同步請求,並完成數據同步,slave2因網絡波動,根本就美歐收到請求,或者接收到輕輕,但是數據同步期間發生錯誤,實際沒有完成同步操作,那麼現在就出現了這麼一種情況,Master和slave1數據時最新的,但是slave2壓根兒就沒有這條數據。假設當前突發狀況,Master和slave1都down了,只剩下了slave2可以對外提供服務,此時,這筆新插入的數據就這麼丟失了。

既然水平擴展出現了一些問題,那就擼起袖子往下幹吧。大致有這麼幾種方案(下面內容屬於延伸擴展):

強一致性

很好理解,要麼三臺機器全部成功,要麼全部失敗。現在client給Master發送一條寫指令,Master寫入成功了,下面給兩臺slave同步數據,因爲Redis是單進程的,爲了實現強一致性,Master只能是阻塞等待兩臺slave同步的結果,如果兩臺都回復Master成功,ok,一次寫入完成。如果slave1通知同步完成,slave2通知失敗,或者半天過去了,都沒給出同步結果,那Master只能認爲此次同步失敗了,爲了強一致性,要麼重試,要麼將已經成功的數據撤銷。但是不論是撤銷數據,還是重試(此時線程還阻塞着呢),對於client來講相當於是服務不可用。由此可見,願景很美好,但是代價實在是太大了,嚴重破壞可用性。

弱一致性

既然可用性怎麼嬌氣,那肯定不能使用同步阻塞了,哎,那就換成異步通知唄。當client數據到達Master之後,Master成功就行,然後Master異步通知兩個slave進行數據同步,但是無法保證兩個slave的數據能完全和Master一致。

最終一致性

在這裏插入圖片描述
假若有一個可靠的第三方,Master將數據同步提交(響應必須足夠快),獲得第三方回執後即可認爲數據同步成功,slave1,slave2的具體同步操作,由第三方異步的去更新,保證最終兩臺從機都能update。

數據讀取問題

Redis讀寫分離後,假設Master已經更新數據,但是slave還沒有來得及完成更新。現有兩個client,一個從Master讀取數據,一個從slave讀取數據,一個讀出了新數據,另一個讀的是老數據。

Master單點問題

無論是主從還是主備,都離不開一個主機Master,問題來了,這個Master本身,還是一臺單點機器,問題又回到原點了…

Redis是如何實現的呢?

解決Master單點問題

想象一下如果是一個人,如何判斷一臺Master是否可用呢?很簡單,與Master建立通信,判斷Master是否可以提供服務。下面,可以給大家介紹下哨兵(Sentinel)了:
在這裏插入圖片描述
相信聰明的你一定注意到了上面哨兵我畫了3個,爲什麼不是1個,也不是兩個呢?

首先我們要明確哨兵存在的意義,是爲了解決Master的單點問題,說直白一點,就是監控當前的Master是否存活,是否可以提供服務。

假設現在一個Master只有一個哨兵監控,如果某一個時間點哨兵與Master之間出現網絡波動,哨兵認爲Master down了,啓動災備,將一臺slave切換成了新Master,其實老Master好好的,這便出現了典型的網絡分區問題,一部分連接仍舊和老Master保持聯繫,一部分新連接則和新Master進行交互,產生不一致。

既然只有一個哨兵不可行,再加一個哨兵會怎麼樣呢?假使在某一時刻一個哨兵連接出現問題,判定Master出現問題,而另外一個哨兵與Master保持着正常連接,判斷Master好好的,一個認爲down了,一個認爲沒問題,到底該聽誰的呢?很明顯,僅僅兩個哨兵也不可行。

至少需要三個哨兵去監控一個Master,當超過其中一半數+1的哨兵認爲Master down了,即投票數大於當前哨兵的一半,纔可以認爲Master是真的down了,應該啓動災備了。

關於哨兵這塊,本章節不擴展開來講了,後續有專門章節詳解哨兵是如果工作,如何發起救援的。

Redis的實現

下面我們以一個簡單的小demo來看下Redis是如何實現數據同步的:
環境:mac os
工具:shell,docker(本人比較懶,不想配置一大堆東西,使用docker容器的默認配置)
說明:本demo使用Redis使用默認配置,修改配置後的結果,與本demo會有些許不一樣。

1.首先,我們在本機模擬建立一個Redis集羣環境。首先啓動4個空白shell:
在這裏插入圖片描述
2.從左到右的4個shell中,分別輸入如下命令(爲了演示效果,我們讓Redis實例進行前端阻塞運行):

左上:
docker run --name master -p 6379:6379 redis
右上:
docker run --name slave1 -p 6380:6380 redis
左下:
docker exec -ti master redis-cli
右下:
docker exec -ti slave1 redis-cli

效果如圖:
在這裏插入圖片描述
我們順利的啓動了兩臺Redis實例,一個是6379,一個是6380,
同時啓動了兩個客戶端,一個連接實例6379,一個連接實例6380

同時,由於docker的沙箱機制,兩個容器之間是不能通信的,我們下面建立一個網橋用於實例之間的雙向通信:

docker network create -d bridge myBridge
docker network connect myBridge master
docker network connect myBridge slave1

ok,至此,我們的準備工作都已經全部完成。

3.爲了演示,我們現在6379中添加部分key:
在這裏插入圖片描述
4.將6380設置爲6379的slave
在連接6380的客戶端中使用跟隨命令:

replicaof 172.17.0.2 6379

ok,執行完畢,我們現在來看下4個shell的顯示界面
在這裏插入圖片描述
在Master的shell中,我們可以看到Master在slave成功連接之後,進行了這麼幾件事情:使用copy-on-write方式,新起了一個後臺線程23執行bgsave(即生成當前Master中全量RDB文件),完成之後,將RDB文件用SYNC命令發送給了slave。

在slave的shell中,我們可以看到,與Master建立連接之後,開始建立SYNC通道,發送ping到Master驗證連接是否通暢,在接收到Master發送過來的RDB數據之後,首先清空了自己的老數據,然後加載RDB數據進入內存。

我們來看下效果:
在這裏插入圖片描述
至此,Master與slave之間,完成了一次全量數據的同步。

5.增量數據同步:
在這裏插入圖片描述
我們在Master中再新增一個key11,然後去slave中查看,可以看到key11已經同步到了slave中

下面我們總結下Redis的主從複製邏輯:

  • 全量數據同步:
    一般使用場景爲slave新連接進Master,或者slave down很長時間,恢復之後重新與Master建立連接。
    全量同步,是需要使用RDB文件的,默認是先將生成的RDB文件保存在磁盤上,等RDB文件全部生成完畢之後,再發送給slave。所以性能會受到磁盤速度的限制,可以在conf中添加配置:repl-diskless-sync no,將生成的RDB文件不落磁盤,直接通過網絡形式發送給slave。
    至於數據同步過程,上面的demo中已經有了詳細過程,這裏就不做過多說明了。
    另外,說明一點,在slave刪除自身舊數據,加載新數據的過程中,默認是阻塞進行的,可以在conf中配置異步進行,在這段時間內,slave仍響應請求,同時以舊數據返回,但是加載新數據集的操作只能是在主線程中,會阻塞salve。
  • 增量同步:
    增量同步相較於全量同步,會稍微複雜一些。
    我們先看下Master和slave下都有哪些信息:
    輸入"info replication"
    在這裏插入圖片描述
    我們可以看到,在Master中,有當前自己的replication ID,以及自己的數據偏移量offset,而在slave中,不僅有自己的replication ID,還有Master的replid以及Master的offset,這些便是最近一次Master同步數據的信息。

Master將自身的複製流傳給slave時,發送多少字節的數據,自身的偏移量offset就會增加多少,目的是當有新的操作修改自己的數據集時,它可以用offset去更新slave的狀態。基本上每一對replication ID和offset,可以確定一個確切的數據版本。

當salve連接到Master之後,使用PSYNC命令發送他們記錄的舊的master replicationID和它們至今爲止處理的offset,這樣Masetr便可以知道,應該從哪個數據開始,將其後續的數據同步給slave

假設這樣一個問題,如果slave最近的同步時間是一小時前,在這1小時內,Master已經更新4G的數據,當slave再次進行同步的時候,是否還是這樣同步呢?答案不是。

從上面的圖中,我們可以看到一個叫"repl_backlog_size"的信息,這個便是Master的操作命令緩衝區,大小時可以配置的,爲了便於理解,可以將它想象成一個環狀的內存空間:
在這裏插入圖片描述
從index=0開始,來一條指令,往環內填入數據,直到填滿整個環狀內存,後續再來一條命令,將之前index=0的地方開始,將老的指令替換爲新的指令。

當slave攜帶offset信息過來之後,Master會根據這個offset,去指令緩衝區內尋找相應的偏移量,如果在緩衝區內找到了,則將其後續指令同步給slave,如果在緩衝區內沒有找到相應的偏移量,則進行RDB全量數據同步。

slave默認是隻讀模式,可以通過conf調整爲讀寫模式。

主從模式下,Redis由於是異步複製,所以無法確保每個slave都收到了數據,總會有數據丟失的可能,這需要看具體的使用場景是否能夠容忍,考研網絡分區的容忍度。

另外,Redis支持部分一致性,通過配置:
min-replicas-to-write xxx
min-replicas-max-lag xxx
設置最小的slave寫入成功的數據,以及相互之間通信的最大超時時間。比如xxx配置爲3,只有當最少3臺salve都寫入成功了,這條數據纔會認爲寫入成功,最終進入Redis數據集。

2.解決Redis容量問題

篇幅有限,在後續的Redis分區章節進行講解,敬請期待。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章