“ Redis使用主從模式和叢集模式來盡量保證緩存服務的高可用
Redis是單線程的,可以通過在單機開多個Redis執行個體,避免CPU多核的浪費,但是單機仍然存在瓶頸。
Redis叢集是Redis的分布式解決方案,當一個服務挂了可以快速的切換到另外一個服務,當遇到單機記憶體、并發等瓶頸時,常使用此方案。
叢集的部署方式也就是Redis cluster,采用主從同步讀寫分離,類似Mysql的主從同步,Redis cluster支撐 N 個 Redis master node,每個master node都可以挂載多個 slave node,這樣整個 Redis就可以橫向擴容了。
如果要支撐更大資料量的緩存,那就橫向擴容更多的 master 節點,每個 master 節點就能存放更多的資料了。
叢集中的節點分為主節點和從節點:隻有主節點負責讀寫請求和叢集資訊的維護;從節點隻進行主節點資料和狀态資訊的複制。
單機的 redis,能夠承載的 QPS 大概就在上萬到幾萬不等。對于緩存來說,一般都是用來支撐讀高并發的。
是以架構做成**主從(master-slave)架構,一主多從,主負責寫,并且将資料複制到其它的 slave 節點,從節點負責讀。**
所有的讀請求全部走從節點。這樣可以輕松實作水準擴容,支撐讀高并發。

Redis主從同步架構
啟動一台slave 的時候,他會發送<code>psync</code>指令給master ,如果是這個slave第一次連接配接到master,會觸發全量複制
master就會啟動一個線程,生成RDB快照,并且把新的寫請求都緩存在記憶體中,
RDB檔案生成後,master會将這個RDB發送給slave,
slave拿到之後寫進本地的磁盤,然後加載進記憶體,然後master會把記憶體裡面緩存的那些新命名都發給slave。
如果全量複制過程中,master-slave 網絡連接配接斷掉,那麼 slave 重新連接配接 master 時,會觸發增量複制。
master 直接從自己的 backlog 中擷取部分丢失的資料,發送給 slave node,預設 backlog就是 1MB。
master 就是根據 slave 發送的 <code>psync</code> 中的<code>offset</code> 來從 backlog 中擷取資料的。
RDB持久化
注意:slave node 如果跟 master node 有網絡故障,斷開了連接配接,會自動重連,連接配接之後 master node 僅會複制給 slave 部分缺少的資料。
sentinel,中文名是哨兵。哨兵是 redis 叢集機構中非常重要的一個元件,本身也是分布式的,作為一個哨兵叢集去運作,互相協同工作,主要有以下功能:
叢集監控:負責監控 redis master 和 slave 程序是否正常工作。
消息通知:如果某個 redis 執行個體有故障,那麼哨兵負責發送消息作為報警通知給管理者。
故障轉移:如果 master node 挂掉了,會自動轉移到 slave node 上。
配置中心:如果故障轉移發生了,通知 client 用戶端新的 master 位址
哨兵必須用三個執行個體去保證自己的健壯性,哨兵+主從的部署架構并不能保證資料不丢失,但是可以保證叢集的高可用。
經典的哨兵叢集如圖所示,M1所在的機器挂了,哨兵還有兩個,可以選舉一個出來執行故障轉移。
但是如果隻有兩個執行個體存在,master當機了, s1和s2兩個哨兵隻要有一個認為你當機了就切換了,并且會選舉出一個哨兵去執行故障
如果M1當機了,S1沒挂那其實是OK的,但是整個機器都挂了呢?哨兵就隻剩下執行個體S2,沒有哨兵去允許故障轉移了,雖然另外一個機器上還有R1,但是故障轉移是不會執行的,無法保證叢集高可用。
經典哨兵叢集
如果一個master被認為當機了,而且大多數的哨兵都允許主備切換,那麼某個哨兵就會執行主備切換操作,此時首先要選舉一個 slave來,會考慮 slave 的一些資訊:
跟 master 斷開連接配接的時長
slave 優先級
複制 offset
run id
如果一個 slave 跟 master 斷開連接配接的時間已經超過了 <code>down-after-milliseconds</code> 的 10 倍,外加 master 當機的時長,那麼 slave 就被認為不适合選舉為 master。
接下來會對 slave 進行排序:
按照 slave 優先級進行排序,slave priority 越低,優先級就越高。
如果 slave priority 相同,那麼看 replica offset,哪個 slave 複制了越多的資料,offset 越靠後,優先級就越高。
如果上面兩個條件都相同,那麼選擇一個 run id 比較小的那個 slave。
分布式資料庫把整個資料按分區規則映射到多個節點,即把資料劃分到多個節點上,每個節點負責整體資料的一個子集。比如我們庫有600條使用者資料,有3個redis節點,将600條分成3份,分别存入到3個redis節點。
常見的分區規則哈希分區和順序分區,redis 叢集使用了哈希分區,順序分區暫用不到,不做具體說明;
哈希分區的基本思路是:對資料的特征值(如key)進行哈希,然後根據哈希值決定資料落在哪個節點。常見的哈希分區包括:哈希取餘分區、一緻性哈希分區、帶虛拟節點的一緻性哈希分區等。
衡量資料分區方法好壞的标準有很多,其中比較重要的兩個因素是**(1)資料分布是否均勻(2)增加或删減節點對資料分布的影響。**
由于哈希的随機性,哈希分區基本可以保證資料分布均勻;是以在比較哈希分區方案時,重點要看增減節點對資料分布的影響。
哈希取餘分區思路非常簡單:計算key的hash值,然後對節點數量進行取餘,進而決定資料映射到哪個節點上。該方案最大的問題是,當新增或删減節點時,節點數量發生變化,系統中所有的資料都需要重新計算映射關系,引發大規模資料遷移。
一緻性雜湊演算法将整個哈希值空間組織成一個虛拟的圓環,如下圖所示,範圍為0-2^32-1;
對于每個資料,根據key計算hash值,确定資料在環上的位置,然後從此位置沿環順時針行走,找到的第一台伺服器就是其應該映射到的伺服器。
一緻性哈希
與哈希取餘分區相比,一緻性哈希分區将增減節點的影響限制在相鄰節點。以上圖為例,如果在node1和node2之間增加node5,則隻有node2中的一部分資料會遷移到node5;如果去掉node2,則原node2中的資料隻會遷移到node4中,隻有node4會受影響。
一緻性哈希分區的主要問題在于,**當節點數量較少時,增加或删減節點,對單個節點的影響可能很大,造成資料的嚴重不平衡。**還是以上圖為例,如果去掉node2,node4中的資料由總資料的1/4左右變為1/2左右,與其他節點相比負載過高。
該方案在一緻性哈希分區的基礎上,引入了虛拟節點的概念。Redis叢集使用的便是該方案,其中的虛拟節點稱為槽(slot)。
槽是介于資料和實際節點之間的虛拟概念;每個實際節點包含一定數量的槽,每個槽包含哈希值在一定範圍内的資料。引入槽以後,資料的映射關系由資料hash->實際節點,變成了資料hash->槽->實際節點。
在使用了槽的一緻性哈希分區中,槽是資料管理和遷移的基本機關。槽解耦了資料和實際節點之間的關系,增加或删除節點對系統的影響很小。
仍以上圖為例,系統中有4個實際節點,假設為其配置設定16個槽(0-15);槽0-3位于node1,4-7位于node2,以此類推。如果此時删除node2,隻需要将槽4-7重新配置設定即可,例如槽4-5配置設定給node1,槽6配置設定給node3,槽7配置設定給node4;可以看出删除node2後,資料在其他節點的分布仍然較為均衡。
槽的數量一般遠小于2^32,遠大于實際節點的數量;在Redis叢集中,槽的數量為16384,
所有的鍵根據哈希函數<code>Hash()=(CRC16[key]&16383)</code>映射到0-16383槽内,共16384個槽位,每個節點維護部分槽及槽所映射的鍵值資料
下面這張圖很好的總結了Redis叢集将資料映射到實際節點的過程:
資料映射到實際節點
(1)Redis對資料的特征值(一般是key)計算哈希值,使用CRC16算法
(2)根據哈希值,計算資料屬于哪個槽。
(3)根據槽與節點的映射關系,計算資料屬于哪個節點。
redis用虛拟槽分區好處:解耦資料與節點關系,節點自身維護槽映射關系,分布式存儲
叢集要作為一個整體工作,離不開節點之間的通信
在哨兵系統中,節點分為資料節點和哨兵節點:前者存儲資料,後者實作額外的控制功能。在叢集中,沒有資料節點與非資料節點之分:所有的節點都存儲資料,也都參與叢集狀态的維護。為此,叢集中的每個節點,都提供了兩個TCP端口:
普通端口:即我們在前面指定的端口(7000等)。普通端口主要用于為用戶端提供服務(與單機節點類似);但在節點間資料遷移時也會使用。
叢集端口:端口号是普通端口+10000(10000是固定值,無法改變),如7000節點的叢集端口為17000。叢集端口隻用于節點之間的通信,如搭建叢集、增減節點、故障轉移等操作時節點間的通信;不要使用用戶端連接配接叢集接口。為了保證叢集可以正常工作,在配置防火牆時,要同時開啟普通端口和叢集端口。
節點間通信,按照通信協定可以分為幾種類型:單對單、廣播、Gossip協定等。重點是廣播和Gossip的對比。
廣播是指向叢集内所有節點發送消息;優點是叢集的收斂速度快(叢集收斂是指叢集内所有節點獲得的叢集資訊是一緻的),缺點是每條消息都要發送給所有節點,CPU、帶寬等消耗較大。
**Gossip協定的特點是:在節點數量有限的網絡中,每個節點都“随機”的與部分節點通信(并不是真正的随機,而是根據特定的規則選擇通信的節點),經過一番雜亂無章的通信,每個節點的狀态很快會達到一緻。**Gossip協定的優點有負載(比廣播)低、去中心化、容錯性高(因為通信有備援)等;缺點主要是叢集的收斂速度慢。
叢集中的節點采用固定頻率(每秒10次)的定時任務進行通信相關的工作:判斷是否需要發送消息及消息類型、确定接收節點、發送消息等。如果叢集狀态發生了變化,如增減節點、槽狀态變更,通過節點間的通信,所有節點會很快得知整個叢集的狀态,使叢集收斂。
節點間發送的消息主要分為5種:meet消息、ping消息、pong消息、fail消息、publish消息。不同的消息類型,通信協定、發送的頻率和時機、接收節點的選擇等是不同的。
MEET消息:在節點握手階段,當節點收到用戶端的CLUSTER MEET指令時,會向新加入的節點發送MEET消息,請求新節點加入到目前叢集;新節點收到MEET消息後會回複一個PONG消息。
PING消息:叢集裡每個節點每秒鐘會選擇部分節點發送PING消息,接收者收到消息後會回複一個PONG消息。PING消息的内容是自身節點和部分其他節點的狀态資訊;作用是彼此交換資訊,以及檢測節點是否線上。** **PING消息使用Gossip協定發送,**接收節點的選擇兼顧了收斂速度和帶寬成本,具體規則如下:(1)随機找5個節點,在其中選擇最久沒有通信的1個節點(2)掃描節點清單,選擇最近一次收到PONG消息時間大于cluster_node_timeout/2的所有節點,防止這些節點長時間未更新。
PONG消息:PONG消息封裝了自身狀态資料。可以分為兩種:第一種是在接到MEET/PING消息後回複的PONG消息;第二種是指節點向叢集廣播PONG消息,這樣其他節點可以獲知該節點的最新資訊,例如故障恢複後新的主節點會廣播PONG消息。
FAIL消息:當一個主節點判斷另一個主節點進入FAIL狀态時,會向叢集廣播這一FAIL消息;接收節點會将這一FAIL消息儲存起來,便于後續的判斷。
PUBLISH消息:節點收到PUBLISH指令後,會先執行該指令,然後向叢集廣播這一消息,接收節點也會執行該PUBLISH指令。
所有的 redis 節點彼此互聯 (PING-PONG 機制),内部使用二進制協定優化傳輸速度和帶寬。
節點的 fail 是通過叢集中超過半數的節點檢測失效或者某個節點主從全挂時才生效。
用戶端與 redis 節點直連,不需要中間 proxy 層。用戶端不需要連接配接叢集所有節點,連接配接叢集中任何一個可用節點即可。
redis-cluster 把所有的實體節點映射到 [0-16383]slot 上。
為了當部分節點失效時,cluster 仍能保持可用,Redis 叢集采用每個節點擁有 1(主服務自身)到 N 個副本的主從模型。類似于 master/slave。
但是 redis cluster 卻不是強一緻性的,因為 cluster 内部 master 和 slave 之間是通過異步複制做資料同步的,複制過程中可能 master 挂了,這就導緻部分資料沒有完全同步至 slave 上,不過這種可能性還是很小的。
叢集選舉過程是叢集中所有 master 參與,如果半數以上 master 節點與目前 master 節點通信逾時,則叢集認為目前 master 節點挂掉
當叢集不可用時, 所有對叢集的操作做都将失敗。以下是會導緻叢集不可用的其中兩種情況:
叢集任意 master 挂掉,并且目前 master 沒有 slave,叢集不可用
叢集超過半數以上 master 挂掉,無論是否有 slave,叢集不可用
以往的一緻性哈希方案,如果我們移除或者新增節點時,雖然說不會導緻全局 key 的 rehash,但是也會影響到部分 key 的失效。Redis Cluster 在可用性和可擴充性上比較重視,如果叢集新增一個節點,在給該節點配置設定槽時,這些槽所屬的源節點和該節點會進行一次 key 的遷移,并且遷移過程中不阻塞叢集服務。如果移除一個節點,同理,我們需要将待移除的節點的 key 遷移到另一個節點上。
那叢集是如何做到 key 遷移不阻塞叢集服務的呢?
key 遷移過程中,涉及到 CLUSTER SETSLOT slot8 MIGRATING node 指令和 CLUSTER SETSLOT slot8 IMPORTING node 指令,前者用于将給定節點 node 中的槽 slot8 遷移出節點,而後者用于将給定槽 slot8 導入到節點 node :
如果一個槽被設定為 MIGRATING 狀态時,原本持有該槽的節點會繼續接受關于這個槽的指令請求,但隻有當鍵存在于該節點時,節點才會處理這個請求。如果指令所使用的鍵不存在于該節點,那麼節點将向用戶端傳回一個 ASK 轉向(redirection)錯誤,告知用戶端,要将指令請求發送到槽的遷移目标節點。
如果一個槽被設定為 IMPORTING 狀态時,節點僅在接收到 ASKING 指令之後,才會接受關于這個槽的指令請求。如果用戶端向節點發送該槽的資料請求,指令為非 ASKING 時,那麼節點會使用 MOVED 轉向錯誤将指令請求轉向至真正負責處理這個槽的節點。
我是一名後端開發工程師,個人公衆号:任冬學程式設計