不想彈好吉他的擼鐵狗,都不是好的程序猿程序員
雖說單機的Redis性能很好,也有完備的持久化機制,那若是你的業務體量真的很大,超過了單機可以承載的上限了怎麼辦?不作任何處理的話Redis掛了怎麼辦?帶着這個問題開始咱們今天的主題-「Redis高可用」,因爲篇幅緣由,本章就只聊聊主從複製。web
爲啥要先從主從複製開始聊,是由於「主從複製」能夠說是整個Redis高可用實現的基石,你能夠先有這麼一個概念,至於具體爲何是基石,這個後面聊到Sentinel和Redis集羣的時候會說到。redis
首先咱們須要知道,對於咱們開發人員來講,爲何須要「主從架構」?一個Redis實例難道不行嗎?數據庫
其實除了開篇提到的負載超過了Redis單機可以處理的上限,還有一種狀況Redis也沒法保證自身的高可用性。那就是即使Redis可以扛住全部流量,可是若是這個Redis進程所在的機器掛了呢?請求會直接調轉槍口,大量的流量會瞬間把你的DB打掛,而後你就能夠背個P0,打包回家了。緩存
並且,假設你對Redis的需求真的超過了單機的容量,你怎麼辦?搞多臺獨立的Redis實例嗎?那若是用戶緩存的數據這一次存在了實例一,下一次若是用戶又訪問到了實例二,難道又要去走一遍DB嗎?除非你可以維護好用戶和Redis實例的對應關係(可是一般這樣的邏輯比較複雜),不然部署多個Redis實例也就失去了它的意義,沒有辦法作到橫向擴展了。服務器
那換成主從架構就能解決這個問題嗎?微信
咱們能夠從一個圖來直觀的瞭解一下。數據結構
在主從同步中,咱們將節點的角色劃分爲master
和slave
,造成「一主多從」。slave對外提供讀操做,而master負責寫操做,造成一個讀寫分離的架構,這樣一來就可以承載更多的業務請求。架構
在多數的業務場景下,對於Redis的「讀操做」都要多於「寫操做」,因此當讀請求量特別大的時候,咱們能夠經過增長slave節點來使Redis扛住更多的流量。併發
你這不行啊老弟,你往master寫數據,那我要是鏈接到slave上去了,不就拿不到以前的數據了?
我這個小標題的不是寫了嗎?「主從複製」,slave會按照某種策略從master同步數據。Redis中咱們能夠經過slaveof
命令讓一個Redis實例去複製(replicate)另一臺Redis的狀態。被複制的Redis實例就是master節點,而執行slaveof
命令的機器就是slave節點。
Redis的主從複製分爲兩個步驟,分別是「同步」和「命令傳播」。
「同步操做」用於將Master節點內存狀態複製給Slave節點,而「命令傳播」則是在同步時,客戶端又執行了一些「寫」操做改變了服務器的狀態,此時master節點的狀態與同步操做執行的時候不一致了,因此須要命令傳播來使master和slave狀態從新一致。
同步的大體的流程以下:
sync
命令
sync
命令以後會執行
bgsave
命令,Redis會fork出一個子進程在後臺生成RDB文件,同時將同步過程當中的寫命令記錄到緩衝區中
爲了讓你們更加清晰的認識到這個過程,咱們經過圖再來了解一下。
🐂🍺,那若是同步完了以後slave又掛了咋辦?slave重啓以後極可能就又跟maste不一致了?
的確是這樣,這就涉及到一個名詞叫「斷點續傳」了。上面討論的是slave第一次鏈接到master,會執行「全量複製」,而針對上面這種狀況,Redis新老版本處理方式不同。
Redis2.8以前,當主從完成了同步以後,slave若是斷線重連,向master發送sync
命令,master會將全量的數據再次同給slave。
可是咱們會發現一個問題,就是大部分數據都是有序的,再次全量同步顯得沒有必要。而在 Redis2.8以後,爲了解決這個問題,便使用了psync
命令來代替sync
。
簡單來講psync命令就是將slave斷線期間master接收到的寫命令所有發送給slave,slave重放以後狀態便與master一致了。
呵呵,就這?那你知道psync具體怎麼實現的嗎?仍是說就只會用用?
psync的實現依賴於主從雙方共同維護的offset
偏移量。
每次master向slave進行「命令傳播」,傳播了多少個字節的數據,就將本身的offset加上傳播的字節數。而slave每次收到多少字節的數據,也會一樣的更新本身的offset。
基於offset,只須要簡單的比對就知道當前主從的狀態是不是一致的了,而後基於offset,將對應偏移量所對應的指令傳播給slave重放便可。因此即便同步的時候slave掛掉了,基於offset,也能達到斷點續傳的效果。
不是吧不是吧,那master也掛了呢?你slave從新啓動以後master的數據也更新了,按照你的說法,這兩永遠不可能達到數據一致了
這個問題Redis的確也有想到,實際上除了offset以外,slave斷線重連以後還會帶上上一個master的實例的runid
,每一個服務實例都有本身的惟一的runid,只要Redis服務重啓,其runid
就會發生改變。
master收到這個runid以後會判斷是否與本身當前的runid一致,若是一致說明斷線以前仍是與本身創建的鏈接,而若是不一致就說明slave斷線期間,master也發生了宕機,此時就須要將數據「全量同步」給slave了。
就算你能解決這個問題,可是你就維護了一個偏移量,偏移量對應的命令從哪兒來?天上掉下來嗎?我哪兒知道這些命令是啥?
的確,咱們須要經過這個offset去拿到真正須要的數據—也就是指令,而Redis是經過「複製積壓緩衝區」來實現的。
名字高大上,實際上就是一隊列。就跟什麼遞歸、輪詢、透傳同樣,聽着高大上,實際上簡單的一匹。言歸正傳,複製積壓緩衝區的默認大小爲1M,Redis在進行「命令傳播」時,除了將寫命令發送給slave,還會將命令寫到「複製積壓緩衝區」內,並和當前的offset關聯起來。這樣一來就可以經過offset獲取到對應的指令了。
可是因爲緩衝區的大小有限,若是slave的斷線時間過久,複製積壓緩衝區內早些時候的指令就已經被新的指令覆蓋掉了,此處能夠理解爲一個隊列,早些時候入隊的元素已經被出隊了。
因爲沒有相對應的offset了,也就沒法獲取指令數據,此時Redis就會進行「全量同步」。固然,若是offset還存在於複製積壓緩衝區中,則按照對應的offset進行「部分同步」。
基於以上的全量、增量的主從複製,可以在master出現故障的狀況下,進行主從的切換,保證服務的正常運行。除此以外還能解決異常狀況下數據丟失的問題。基於讀寫分離的策略還可以提升整個Redis服務的併發量。
可別吹了,你說的這個什麼「主從複製」就沒啥缺點嗎?
實際上是有的,例如剛剛提到的主從的切換,若是不用現成的「HA」框架,這個過程須要程序員本身手動的完成,同時通知服務調用方Redis的IP發生了變化,這個過程能夠說是十分的複雜,甚至還可能涉及到代碼配置的改動。並且以前的slave複製的可都是掛掉的master,還得去slave上更改其複製的主庫,就更加複雜了。
除此以外,雖然實現了讀寫分離,可是因爲是「一主多從」的架構,集羣的「讀請求」能夠擴展,可是「寫請求」的併發是有上限的,那就是master可以扛住的上限,這個沒有辦法擴展。
好了,本期的分享就到此結束了,咱們下期再見。
若是你以爲這篇文章對你有幫助,還麻煩「點個贊」,「關個注」,「分個享」,「留個言」
也能夠微信搜索公衆號【「SH的全棧筆記」】,關注公衆號提早閱讀其餘的文章
往期文章