1. 程式人生 > 其它 >3萬字聊聊什麼是Redis(二)

3萬字聊聊什麼是Redis(二)

無標題大家好,我是Leo

繼上篇Redis技術總結一,我們繼續聊聊Redis的相關技術!

上一篇我們介紹了

  1. Redis五大型別的底層實現
  2. 資料量的增加,效能變慢的問題分析,原理剖析
  3. Redis單執行緒與多執行緒這個高頻話題
  4. IO模型,多路複用機制
  5. AOF寫入策略,重寫機制
  6. RDB寫時複製技術

推薦閱讀

3萬字聊聊什麼是MySQL

3萬字聊聊什麼是Redis(一)

思路

Redis主從庫的由來

隨著資料量的增大,單臺Redis無法很好的提供讀寫快取服務了。於是就採用多臺Redis完成更大的訪問請求,那麼多臺Redis在提供服務時,資料肯定是要一致的。

Redis的處理方案是採用主從庫模式,主從庫之間採用的是讀寫分離方式,以保證資料副本的一致性。

讀操作 :主從庫都可以接收,因為讀操作不影響主從庫的資料一致問題

寫操作 :如果在從庫寫,那麼主庫資料就不一致了。

如果不採用讀寫分離模式,那麼我們就需要用其他方案解決資料一致性問題,比如加鎖,多個Redis例項協商等等。這樣的開銷是非常大的,對於高效能的Redis來說顯然是不能接受的。

如果採用讀寫分離模式,所有的寫請求都打到主庫上,主庫再利用RDB檔案同步給從庫。達到資料的一致性!就可以省去一些不必要的開銷了。

主從庫資料如何實現一致

聊到Redis的主從庫資料的一致性,我們可以先聊聊MySQL是如何實現主從庫資料一致性的。

MySQL是藉助binlog實現資料同步的,binlog主要有三種格式,statement,row,mixed。statement過於簡單,row過於複雜資料量大,mixed中和了一下適合同步傳輸。

介紹了binlog三種格式,具體的流程是首先會在從庫上執行 change master 設計好主從庫之後,再執行 start slave 主庫會發送從庫binlog檔案進行同步。

Redis這裡也是一樣的邏輯,通過 replicaof 實現從庫認主。認主完成之後主庫藉助RDB檔案進行資料同步操作。首先fork子執行緒進行生成RDB檔案,生成RDB檔案主要有兩種形式,savebgsave

  • save:在主執行緒中執行,會導致阻塞;
  • bgsave:建立一個子程序,專門用於寫入 RDB 檔案,避免了主執行緒的阻塞,這也是 Redis RDB 檔案生成的預設配置。

一般我們都採用bgsave生成RDB檔案,提升效能減少主執行緒阻塞!

生成完RDB就該進行資料同步了,接下來我們分析一下資料同步這個過程中都發生了哪些事情。

第一部分 肯定是主從庫間建立連線,協商同步的過程,主要為了全量複製做準備。主從庫建立連線之後,從庫給主庫傳送了一個 psync ID -1 命令

  • 這裡的ID是簡寫,它是每個Redis例項啟動時都會自動生成的一個隨機ID也叫 runID,也是每個例項的唯一標識
  • -1,他是同步的偏移量,也可以說是進度下標,第一次複製時,傳-1就代表是全量同步,第二次之後就不是-1了,就是當前的複製下標了,比如從庫已經同步到5000了,從庫傳送主庫的時候就會從5000開始進行生成RDB檔案進行增量同步。

第二部分 從庫收到主庫的RDB檔案後,如果是第一次同步的話,會先清空當前從庫的資料庫然後再載入RDB檔案。這是因為從庫在通過 replicaof 命令開始和主庫同步前,可能儲存了其他資料。為了避免之前資料的影響,從庫需要先把當前資料庫清空。

第三部分 當主庫在生成RDB期間,仍然有寫請求,就會導致資料不一致,Redis這裡的處理是開闢一個replication buffer 緩衝區,記錄RDB檔案生成後的主庫寫操作。第三部分就是把這部分少量的資料同步到從庫上。

擴充套件1:除了主從同步,還有一種為了提升效能而誕生的一種同步是主從從模式。因為在主執行緒fork子執行緒後生成RDB檔案是要消耗主執行緒資源的,如果存在多個從庫的話,那麼主庫恐怕要一直fork和傳送RDB。我們可以在部署主從叢集時,手動選擇一個記憶體資源配置較高的從庫作為,從庫與從庫的資料同步源。然後讓他們建立主從關係。

擴充套件2:一旦在傳輸過程沖斷連了,可以通過複製下標進行重新資料同步。就不需要每次都走全量備份了。

高可用性體現在哪

Redis的高可用體現在哨兵機制,哨兵機制是等主從庫掛了之後,通過哨兵機制可以自動切換選舉出新的主庫,然後進行資料同步,達到一致性。最終繼續為使用者提供服務。

哨兵主要負責的就是三個任務:監控、選主(選擇主庫)和通知。

監控: 哨兵會週期性的給所有的主從庫傳送ping命令,監測它們是否處於線上執行狀態,

  1. 如果從庫沒有響應ping命令,哨兵就會把從庫標記為下線狀態。
  2. 如果主庫沒有響應ping命令,哨兵就會判斷主庫下線並且開始自動切換主庫。

選主 :主庫掛了之後,哨兵就需要在很多從庫中,按照一定的規則選擇出一個從庫例項,把它作為新的主庫。

通知 :主庫重新誕生之後,哨兵就會通知從庫,告訴它們新主庫的資訊。讓他們執行replicaof 命令。重新建立之後開始資料同步流程。

一系列流程之後,主庫出現了,從庫也正常了。一切又回到了出故障之前的那個狀態!

如何選定新主庫

在上面高可用中,臨時加了一個技術點,這個知識點就是在哨兵選主時,選擇規則的介紹。

哨兵選主庫時,一般都稱為 "篩選+打分"

篩選:(網路波動)除了檢查從庫當前的線上狀態還要判斷它網路的連線狀態。如果從庫和主庫響應過慢,並且超過了一定的閾值,那麼肯定是不能選擇該從庫充當我們的主庫的。因為一旦該從庫選擇主庫,一旦在後續的寫入操作,資料同步操作中網路波動大,或者直接斷開連線了,我們還需要重新做一下選擇,通知,同步等。這樣效能是非常低效的!

打分:(擇偶標準)主要有三點如下

  • 優先順序最高的從庫得分高:使用者可以通過 slave-priority 配置項,給不同的從庫設定不同的優先順序,比如不同的從庫中的記憶體配置,CPU配置等
  • 同步進度:一般選擇一個從庫為主庫,如果我們從庫的資料同步進度更接近與前主庫,那麼從庫切換成主庫之後,資料同步的時間消耗更低。效能會更好一些。
  • ID 號小的從庫得分高。(Redis的預設規定沒啥好說的)。在優先順序和複製進度都相同的情況下,ID 號最小的從庫得分最高,會被選為新主庫。

哨兵掛了,主從還能切換嗎

答案是可以的。哨兵叢集中的一個哨兵例項掛了,主從依然還是可以切換的,因為我們在配置哨兵資訊的時,我們只需要設定主庫的IP和埠,並沒有配置其他的哨兵連線資訊。

那麼其他哨兵是如何知道彼此的地址的呢?

這應該就需要我們先了解一下 pub/sub機制的哨兵叢集組成 。翻譯一下分別是釋出/訂閱機制。

我們先講一個對應的白話文故事,pub/sub機制就是幾年前QQ非常火爆的QQ群功能,一個一個加好友交友也好,處理事情也好,都是比較麻煩的,如果說群主建立一個QQ群,然後拉自己的好友。拉進來之後,只要是這個群的人員都能收到任意好友傳送的資訊。這與Redis的pub/sub機制類似。

回到Redis中!哨兵只要和主庫建立了連線,就可以在主庫上釋出訊息了,同時也可以從主庫訂閱訊息,獲取其他哨兵釋出的連線資訊。當多個哨兵都在主庫上釋出了和訂閱了資訊後,就能知道彼此的IP地址和埠了 。

釋出和訂閱一直所說的頻道資訊,就類似於不同的群號,接收不同的好友資訊一樣。下面我們實踐一下,必須是兩個視窗或者多個Redis才能進行測試。

  • 第一個視窗負責訂閱一個頻道叫huanshao,訂閱之後就處於等待接收的狀態了。
  • 第二個視窗是用於傳送訊息的,往huanshao這個頻道傳送一個HelloWord然後第一個視窗自動就接收了

哨兵叢集中每個哨兵都拿到了所需要的IP地址和埠號,哨兵除了要監測主庫外還需要監測從庫,因為主庫掛了,要從從庫中選舉一個成為主庫。那麼從庫的資訊哨兵如何拿到呢?

可以通過哨兵告訴主庫傳送info命令來完成!主庫執行了info命令就會把當前的從庫資訊返給哨兵。哨兵拿到了從庫的IP地址和埠號一切就都好辦了。

上述哨兵叢集拿到了主從庫的資訊,在整個主從庫切換這些不止是這些,還有重要的一步就是通知客戶端修改主庫資訊。

和上述獲取從庫資訊一樣,通過不同的頻道獲取不同的訊息,下面列舉幾個常用的頻道事件

主庫下線事件

  • +sdown(例項進入主觀下線狀態)
  • -sdown(例項退出主觀下線狀態)
  • +odown(例項進入客觀下線狀態)
  • -odown(例項退出下線狀態)

從庫重新配置事件

  • +slave-reconf-sent(哨兵傳送SLAVEOF命令重新配置從庫)
  • +slave-reconf-inprog(從庫配置了新主庫,但尚未進行同步)
  • +slave-reconf-done(從庫配置了新主庫,且和新主庫完成同步)

新主庫切換

  • +switch-master(主庫地址發生變化)

知道了頻道資訊,就可以讓客戶端訂閱相關資訊,一旦哨兵監測出主庫掛了,通過選舉出新主庫之後,以事件的方式通知客戶端。就可以實現短暫宕機後的服務恢復了!

Redis訂閱命令

SUBSCRIBE

訂閱所有事件

PSUBSCRIBE  *

馬上結束了,大家再堅持一下! 簡單總結一下,哨兵掛了之後,其他哨兵拿到了主從庫的資訊,同時通過頻道事件的方式通知客戶端重新繫結主庫。那麼選主時由誰來選?

一般應用哨兵我們都會採用哨兵叢集,因為只通過1個哨兵例項的話,往往適得其反,如果當哨兵傳送ping命令給主庫時,那個時刻主庫剛好網路不好,或者正常處理比較大的資料延誤了給哨兵響應資訊,那麼哨兵就會認為當前主庫掛了,就會給他設為主觀/客觀下線。然後就game over了。

於是引用多哨兵例項共同監控,這裡我們設為哨兵A,哨兵B,哨兵C。

當哨兵A發現某個主庫掛了,那麼它會把這個主庫設為主觀下線,然後過段時間哨兵B也會給主庫傳送ping命令,當哨兵B也發現了這個主庫掛了時,也會給他設為主觀下線。在多個哨兵例項中如果有一半以上的哨兵都認為這個主庫掛了。那麼這個主庫就真的掛了,會被設為客觀下線。

判斷出下線之後,多個例項哨兵就會先推選出一個執行leader。由其中一個哨兵來處理後續的通知相關操作。

這就類似於我們學生時代的小組組長一樣,由一個小組6個人共同投票選舉一個人,為這個小組的組長。如果選擇同一個人的票數大於小組總人數。那麼這個人就是組長,在Redis中這個人就是哨兵叢集中的leader,由它來執行操作。

切片叢集解決了什麼

切片叢集也叫作分片叢集,就是啟動多個Redis例項組成一個叢集,然後按照一定的規則把收到的資料劃分成多份,每一份用一個例項來儲存資料。

如果我們生產環境有50G的資料,全部放入一臺Redis例項的話,肯定是成本比較高,而且很多地方不好把控的。於是我們就把50個G的資料分成10份,每個Redis例項存5個G。

從效能上來分析,每個Redis只需要處理5個G的資料,也是非常快的

從硬體上來分析,每個Redis只需要一般配置就可以達到我們的需求。

從擴充套件上來分析,隨著資料的增多,我們只需要不斷加Redis例項就夠了。也是方便擴充套件的

單機跟叢集最難處理的點就是

  • 分散式的一致性,
  • 資料在多個例項上如何分佈,
  • 客戶端如何得到自己想到的資料存在哪個例項上。

下面我們先從 資料如何分佈上 進行介紹。我們可以採用Redis Cluster方案。

Redis Cluster 方案採用雜湊槽,來處理資料和例項之間的對映關係。每個鍵值對都會根據它的 key,被對映到一個雜湊槽中。主要分兩步實現

  • 首先根據鍵值對的 key,按照CRC16 演算法計算一個 16 bit 的值;
  • 然後,再用這個 16bit 值對 切片叢集的雜湊槽總數取模,得到模數,每個模數代表一個相應編號的雜湊槽。

接下來介紹一下 客戶端如何定位資料

Redis 例項會把自己的雜湊槽資訊發給和它相連線的其它例項,來完成雜湊槽分配資訊的擴散。當例項之間相互連線後,每個例項就有所有雜湊槽的對映關係了。

客戶端收到雜湊槽資訊後,會把雜湊槽資訊快取在本地。當客戶端請求鍵值對時,會先計算鍵所對應的雜湊槽,然後就可以給相應的例項傳送請求了。

雜湊值隨著資料的增多與減少並不是一成不變的,任何的增多與減少Redis都需要重新分配雜湊槽。同時為了資料能均勻的分散在多個例項上,Redis也會把雜湊槽在例項上重新分佈一遍。

Redis的例項與例項之後可以通過相互傳遞訊息獲取最新的雜湊槽分配資訊,但是客戶端無法感知,這就導致客戶端的快取資料與Redis的雜湊槽會有不一致的情況。如何解決 ?

Redis Cluster 方案提供了一種 重定向機制,所謂的“重定向”,就是指,客戶端給一個例項傳送資料讀寫操作時,這個例項上並沒有相應的資料時,Redis會給客戶端傳送一個MOVED命令,這個MOVED命令就包含了新例項的IP和埠。然後客戶端要再給一個新例項傳送操作命令就拿到了自己想要的資料了。

GET hello:key
(error) MOVED 13320 172.16.19.5:6379

細節擴充套件

  • Redis給客戶端返回一個新例項資訊,客戶端再次請求時,同時也會修改本地的快取,把當前的key更新到快取中。
  • 如果客戶端請求的這個key剛好遇到了重新分配雜湊槽途中,且資料還沒有完全遷移完。就會返回ACK報錯資訊,這個命令的意思是,讓這個例項允許執行客戶端接下來發送的命令。然後,客戶端再向這個例項傳送 GET 命令,以讀取資料。
GET hello:key
(error) ASK 13320 172.16.19.5:6379

ASK 命令表示兩層含義:第一,表明資料還在遷移中;第二,ASK 命令把客戶端所請求資料的最新例項地址返回給客戶端,此時,客戶端需要給Redis例項 傳送 ASKING 命令,然後再發送操作命令。

和 MOVED 命令不同,ASK 命令並不會更新客戶端快取的雜湊槽分配資訊。所以如果客戶端再次請求正在遷移的key,它還是會給例項 2 傳送請求。這也就是說,ASK 命令的作用只是讓客戶端能給新例項傳送一次請求,而不像 MOVED 命令那樣,會更改本地快取,讓後續所有命令都發往新例項。

CAP原理

  • C - Consistent ,一致性 ,訪問所有的節點得到的資料應該是一樣的。注意,這裡的一致性指的是強一致性,也就是資料更新完,訪問任何節點看到的資料完全一致,要和弱一致性,最終一致性區分開來。
  • A - Availability ,可用性,所有的節點都保持高可用性。注意,這裡的高可用還包括不能出現延遲,比如如果節點B由於等待資料同步而阻塞請求,那麼節點B就不滿足高可用性。也就是說,任何沒有發生故障的服務必須在有限的時間內返回合理的結果集。
  • P - Partition tolerance ,分割槽容忍性 這裡的分割槽是指網路意義上的分割槽。由於網路是不可靠的,所有節點之間很可能出現無法通訊的情況,在節點不能通訊時,要保證系統可以繼續正常服務。

一個系統中不可能同時滿足C,A,P三個條件,所以系統架構師在設計系統時,不要將精力浪費在如何設計能滿足三者的完美分散式系統,而是應該進行取捨。由於網路的不可靠性質,大多數開源的分散式系統都會實現P,也就是分割槽容忍性,之後在C和A中做抉擇。

接下來我們分三個場景分析:

  • 在保證C和P的情況下:為了保證資料一致性,data1需要將資料複製給data2,即data1和data2需要進行通訊。但是由於網路是不可靠的,我們系統有保證了分割槽容忍性,也就是說這個系統是可以容忍網路的不可靠的。這時候data2就不一定能及時的收到data1的資料複製訊息,當有請求向data2訪問number資料時,為了保證資料的一致性,data2只能阻塞等待資料真正同步完成後再返回,這時候就沒辦法保證高可用性了。

    所以,在保證C和P的情況下,是無法同時保證A的。

  • 在保證A和P的情況下:為了保證高可用性,data1和data2都有在有限時間內返回。同樣由於網路的不可靠,在有限時間內,data2有可能還沒收到data1發來的資料更新訊息,這時候返回給客戶端的可能是舊的資料,和訪問data1的資料是不一致的,也就是違法了C。

    也就是說,在保證A和P的情況下,是無法同時保證C的。

  • 在保證A和C的情況下:如果要保證高可用和一致性,只有在網路情況良好且可靠的情況下才能實現。這樣data1才能立即將更新訊息傳送給data2。但是我們都知道網路是不可靠的,是會存在丟包的情況的。所以要滿足即時可靠更新,只有將data1和data2放到一個區內才可以,也就喪失了P這個保證。其實這時候整個系統也不能算是一個分散式系統了。

下述圖片是CAP原理在各個系統中的應用

結尾

每個知識點都是自己整理濃縮表達出來的,部分有些不容易懂的地方請及時指出,我們一起共同進步!

看到這裡,應該都是真粉了,點贊+分享+在看+關注 就是對我最大支援。歡少的成長之路 感謝你