高可用有兩個含義:一是數據盡量不丟失,二是保證服務盡可能可用。 AOF 和 RDB 數據持久化保證了數據盡量不丟失,那么多節點來保證服務盡可能提供服務。
一般在實際生產中,服務不會部署成單節點,主要是有三個原因.
- 容易出現單點故障,導致服務不可用
- 單節點處理所有的請求,吞吐量有限
- 單節點容量有限
為了實現高可用,通常的做法是,將數據庫復制多個副本以部署在不同的服務器上,其中一台掛了也可以繼續提供服務。Redis 實現高可用有三種部署模式:主從模式,哨兵模式,
集群模式。
一、主從模式
既然一台服務宕機了會導致提供不可用,那是不是可以考慮多台就可以解決了。Redis 提供了主從模式。通過主從復制,將數據冗余一份復制到其他 Redis 服務器。
Master節點,負責讀寫操作,Slave節點,只負責讀操作。
1、主從復制原理
主從模式采用了讀寫分離,所有數據的寫操作只會在Master庫上進行,Master庫有了最新的數據后,會同步給Slave庫,這樣,主從庫的數據就是一致的。
這里要思考是主從庫同步是如何完成的?Master庫數據是一次性傳給Slave庫,還是分批同步的?正常運行中又怎么同步呢?要是主從庫間的網絡斷連了,重新連接后需要再次全量同步還是只需部分同步呢?
主從復制包括全量復制,增量復制兩種。redis2.8版本之后還支持部分同步。
(1) 全量同步
一般當Slave第一次啟動連接Master,認為是第一次連接,就采用全量復制,全量復制流程如下:
完成上面幾個步驟后就完成了Salve節點初始化的所有操作,Slave服務器此時可以接收來自用戶的讀請求。
redis2.8版本之后,已經使用psync來替代sync,因為sync命令非常消耗系統資源,而且不支持部分同步,psync的效率更高,支持部分同步,有關部分同步下面細說。
(2) 增量同步
Redis增量復制是指Slave初始化后開始正常工作時,Master服務器發生的寫操作同步到Slave服務器的過程。
增量復制的過程主要是Master服務器每執行一個寫命令就會向從服務器發送相同的寫命令,從服務器接收並執行收到的寫命令。
(3) 部分同步
在redis 2.8版本之前,並不支持部分同步,當主從服務器之間的連接斷掉之后,Master服務器和Slave服務器之間必須進行全量數據同步,此時Slave服務器會清空所有數據,
再次加載Master的RDB文件。但是從redis 2.8開始,即使主從連接中途斷掉,也不一定需要進行全量同步,它可以支持部分同步,來提高效率。
它的工作原理大致是這樣:
通過這個圖我們再來理解下 為什么2.8部分可以實現部分同步
- Slave節點根據當前狀態,發送 psync 命令給 Master節點:
-
如果Slave節點從未執行過 replicaof,則Slave節點發送
psync ? -1,向Master節點發送全量復制請求; -
如果Slave節點之前執行過 replicaof 則發送
psync <runID> <offset>, runID 是上次復制保存的Master節點 runID,offset 是上次復制截至時Slave節點保存的復制偏移量。
- Master節點根據接受到的
psync命令和當前服務器狀態,決定執行全量復制還是部分復制:
- runID 與Slave節點發送的 runID 相同,且Slave節點發送的 slave_repl_offset 之后的數據在 repl_backlog_buffer 緩沖區中都存在,則回復
CONTINUE,表示將進行
部分復制,Slave節點等待Master節點發送其缺少的數據即可;
- runID 與Slave節點發送的 runID 不同,或者Slave節點發送的 slave_repl_offset 之后的數據已不在Master節點的 repl_backlog_buffer緩沖區中 (在隊列中被擠出了),
則回復Slave節點 FULLRESYNC <runid> <offset>,表示要進行全量復制,其中 runID 表示Master節點當前的 runID,offset 表示Master節點當前的 offset,Slave節點
保存這兩個值,以備使用。
一個Slave庫如果和Master庫斷連時間過長,造成它在Master庫 repl_backlog_buffer的 slave_repl_offset 位置上的數據已經被覆蓋掉了,此時Slave庫和Master庫間將進行
全量復制。
2、主從模式的優缺點
優點
-
做到讀寫分離,提高服務器性能。Salve可以分載Master的讀操作壓力,當然寫服務依然必須由Master來完成;
-
當Master節點服務掛了,可以讓Slave變成Master節點繼續提供服務;
缺點
-
在主從模式中,一旦Master節點由於故障不能提供服務,需要人工將Slave節點晉升為Master節點,同時還要通知應用方更新Master節點地址。顯然,多數業務場景都不能接受這種故障處理方式;
-
redis的Master節點和Slave節點中的數據是一樣的,降低的內存的可用性,而且存儲能力也有限。
-
主從復制寫還都是在Master節點,所以寫的壓力並沒有減少。
因此,主從復制其實並不能滿足我們高可用的要求。
二、哨兵模式
在主從模式中,一旦Master節點由於故障不能提供服務,需要人工將Slave節點晉升為Master節點。顯然,多數業務場景都不能接受這種故障處理方式。Redis從2.8開始正式提供了
Redis Sentinel(哨兵)架構來解決這個問題。
哨兵模式,由一個或多個Sentinel實例組成的Sentinel系統,它可以監視所有的Master節點和Slave節點,並在被監視的Master節點進入下線狀態時,自動將下線Master服務器
屬下的某個Slave節點升級為新的Master節點。但是呢,一個哨兵進程對Redis節點進行監控,就可能會出現問題(單點問題),因此,可以使用多個哨兵來進行監控Redis節點,
並且各個哨兵之間還會進行監控。
sentinel是一種特殊的redis實例,它不存儲數據,只對集群進行監控。
簡單來說,哨兵模式就三個作用:
-
通過發送命令,等待Redis服務器返回監控其運行狀態,包括Master服務器和Slave服務器;
-
當哨兵監測到Master節點宕機,會自動將Slaver節點切換成Master節點,然后通過發布訂閱模式通知其他的Slave節點,修改配置文件,讓它們切換主機;
-
如果是只有一個哨兵對進程對Redis服務器進行監控,也可能會出現問題,為此,我們可以使用多個哨兵進行監控。它們之間還會相互監控,從而達到高可用。
1、哨兵主要工作任務
哨兵主要有三個定時監控任務完成對各節點的發現和監控。
任務1:每個哨兵節點每 10 秒會向Master節點和Slave節點發送 info 命令獲取最拓撲結構圖,哨兵配置時只要配置對Master節點的監控即可,通過向Master節點發送info,
獲取Slave節點的信息,並當有新的Slave節點加入時可以馬上感知到
任務2,每個哨兵節點每隔 2 秒會向redis 數據節點的指定頻道上發送該哨兵節點對於Master節點的判斷以及當前哨兵節點的信息,同時每個哨兵節點也會訂閱該頻道,來了解
其它哨兵節點的信息及對Master節點的判斷,其實就是通過消息publish 和subscribe 來完成的;
任務3,每隔 1 秒每個哨兵會向Master節點、Slave節點及其余哨兵節點發送一次ping 命令做一次心跳檢測,這個也是哨兵用來判斷節點是否正常的重要依據
2、哨兵發現服務下線
這里可以分為 哨兵主觀下線 和 哨兵客觀下線
哨兵主觀下線
上面說過哨兵節點每隔 1 秒對Master節點和Slave節點、其它哨兵節點發送 ping 做心跳檢測,當這些心跳檢測時間超過 down-after-milliseconds 時,哨兵節點則認為
該節點 錯誤或下線,這叫主觀下線;
當然這但不代表這個master真的不能用(有可能網絡波動導致該哨兵與服務連接異常),所以主觀下線是不可靠的,可能存在誤判。
哨兵客觀下線
當主觀下線的節點是Mater節點時,此時該哨兵 節點會通過指令sentinelis-masterdown-by-addr 尋求其它哨兵節點對Master節點的判斷,當超過quorum(法定人數)
個數,此時哨兵節點則認為該Master節點確實有問題,這樣就客觀下線了,大部分哨兵節點都同意下線操作,也就說是客觀下線
3、自動故障轉移機制
如果哨兵客觀下線某Master,那是不是接下來要選舉新的Master,這個工作只要一個哨兵完成即可,所以首先要做的是選舉一個哨兵領導者。
1) 領導者選舉
原因:只有一個sentinel節點完成故障轉移所以需要選舉。 選舉通過sentinelis-master-down-by-addr命令希望成為領導者:
-
每個做主觀下線的Sentinel節點向其他Sentinel節點發送命令,要求將它設置為領導者
-
收到命令的Sentinel節點如果沒有同意通過其他Sentinel節點發送的命令,那么將同意該請求,否則拒絕
-
如果該Sentinel節點發現自己的票數已經超過Sentinel集合半數且超過quorum,則將成為領導者
-
如果此過程有多個Sentinel節點成為了領導者,那么將等待一段時間重新選舉
2) 在從節點中選擇新的Master節點
sentinel狀態數據結構中保存了主服務的所有從服務信息,領頭sentinel按照如下的規則從從服務列表中挑選出新的主服務
-
過濾掉主觀下線的節點
-
選擇slave-priority最高的節點,如果由則返回沒有就繼續選擇
-
選擇出復制偏移量最大的系節點,因為復制偏移量越大則數據復制的越完整,如果由就返回了,沒有就繼續
-
選擇run_id最小的節點
3) 更新主從狀態
通過slaveof no one命令,讓選出來的Slave節點成為Master節點;並通過slaveof命令讓其他節點成為其Slave節點。
將已下線的Master節點設置成新的Master節點的Slave節點,當其回復正常時,復制新的Master節點,變成新的Master節點的Slave節點
4、腦裂導致數據丟失
1) 什么是腦裂
腦裂 也就是說,某個 Master 所在機器突然脫離了正常的網絡,跟其他 slave 機器不能連接,但是實際上 master 還運行着。此時哨兵可能就會認為 master 宕機了,然后開啟
選舉,將其他 slave 切換成了 master。這個時候,集群里就會有兩個 Master ,也就是所謂的腦裂。
此時雖然某個 slave 被切換成了 master,但是可能 client 還沒來得及切換到新的 master,還繼續向舊 master 寫數據。因此舊 master 再次恢復的時候,會被作為一個 slave
掛到新的 master 上去,自己的數據會清空,重新從新的 master 復制數據。而新的 master 並沒有后來 client 寫入的數據,因此,這部分數據也就丟失了。
2) 如果解決腦裂問題
Redis 已經提供了兩個配置項來限制Master庫的請求處理,分別是 min-slaves-to-write 和 min-slaves-max-lag。 (2.8以后改為min-replicas-to-write 和 min-replicas-max-lag )
min-slaves-to-write 1
min-slaves-max-lag 10
如上兩個配置:要求至少有 1 個 slave,數據復制和同步的延遲不能超過 10 秒,如果超過 1 個 slave,數據復制和同步的延遲都超過了 10 秒鍾,那么這個時候,master 就
不會再接收任何請求了。
這樣一配置的話,就算你的Master庫是假故障,那它在假故障期間也無法響應哨兵心跳,也不能和Slave庫進行同步,自然也就無法和Slave庫進行 ACK 確認了。原Master庫就
會被限制接收客戶端請求,客戶端也就不能在原Master庫中寫入新數據了。
當然這個配置做不到讓數據一點也不丟失,而是讓數據盡可能的少丟失。
5、哨兵模式的優缺點
優點
- 哨兵模式是基於主從模式的,所有主從的優點,哨兵模式都具有。
- 主從可以自動切換,系統更健壯,可用性更高。
缺點
- 具有主從模式的缺點,每台機器上的數據是一樣的,內存的可用性較低。
- 還要多維護一套哨兵模式,實現起來也變的更加復雜增加維護成本。
- Redis較難支持在線擴容,在集群容量達到上限時在線擴容會變得很復雜。
三、Cluster集群模式
先說一個誤區:Redis的集群模式本身沒有使用一致性hash算法,而是使用slots插槽。因為我查了很多資料都是這么說的,至於為什么使用slots插槽,我個人理解slots插槽多少
個是固定的,這樣更加方便數據遷移。
哨兵模式基於主從模式,實現讀寫分離,它還可以自動切換,系統可用性更高。但是它每個節點存儲的數據是一樣的,浪費內存。因此,在Redis3.0后Cluster集群應運而生,
它實現了Redis的分布式存儲。對數據進行分片,也就是說每台Redis節點上存儲不同的內容,來解決在線擴容的問題。
一個Redis Cluster由多個Redis節點構成,節點組內部分為主備兩類節點,對應master和slave節點。兩者數據准實時一致,通過異步化的主備復制機制來保證。
一個節點組有且只有一個master節點,同時可以有0到多個slave節點,在這個節點組中只有master節點對用戶提供些服務,讀服務可以由master或者slave提供。如上圖中,
包含三個master節點以及三個master對應的slave節點,一般一組集群至少要6個節點才能保證完整的高可用。
其中三個master會分配不同的slot(表示數據分片區間),當master出現故障時,slave會自動選舉成為master頂替Master節點繼續提供服務。
1、集群的一些特點
- redis cluster模式采用了無中心節點的方式來實現,每個Master節點都會與其它Master節點保持連接。節點間通過gossip協議交換彼此的信息,同時每個Master節點又有
一個或多個Slave節點;
-
客戶端連接集群時,直接與redis集群的每個Master節點連接,根據hash算法取模將key存儲在不同的哈希槽上;
-
在集群中采用數據分片的方式,將redis集群分為16384個哈希槽。如下圖所示,這些哈希槽分別存儲於三個Master節點中:
- Master1負責0~5460號哈希槽
- Master2負責5461~10922號哈希槽
- Master3負責10922~16383號哈希槽
- 每個節點會保存一份數據分布表,節點會將自己的slot信息發送給其他節點,節點間不停的傳遞數據分布表;
2、Master節點故障處理方式
redis 集群中Master節點故障處理方式與哨兵模式較為相像,當約定時間內某節點無法與集群中的另一個節點順利完成ping消息通信時,則將該節點標記為主觀下線狀態,同時
將這個信息向整個集群廣播。
如果一個節點收到某個節點失聯的數量達到了集群的大多數時,那么將該節點標記為客觀下線狀態,並向集群廣播下線節點的fail消息。然后立即對該故障節點進行主從切換。
等到原來的Master節點恢復后,會自動成為新Master節點的Slave節點。如果Master節點沒有Slave節點,那么當它發生故障時,集群就將處於不可用狀態。
3、擴容問題
在cluster中我們如何動態上線某個節點呢。當集群中加入某個節點時,哈希槽又是如何來進行分配的?當集群中加入新節點時,會與集群中的某個節點進行握手,該節點會把集群
內的其它節點信息通過gossip協議發送給新節點,新節點與這些節點完成握手后加入到集群中。
然后集群中的節點會各取一部分哈希槽分配給新節點,如下圖:
- Master1負責1365-5460
- Master2負責6827-10922
- Master3負責12288-16383
- Master4負責0-1364,5461-6826,10923-12287
當集群中要刪除節點時,只需要將節點中的所有哈希槽移動到其它節點,然后再移除空白(不包含任何哈希槽)的節點就可以了。
4、關於 gossip協議
有關gossip協議這里需要單獨解釋下
在整個redis cluster架構中,如果出現以下情況
- 新加入節點
- slot遷移
- 節點宕機
- slave選舉成為master
我們希望這些變化能夠讓整個集群中的每個節點都能夠盡快發現,傳播到整個集群並且集群中所有節點達成一致,那么各個節點之間就需要相互連通並且攜帶相關狀態數據進行
傳播,按照正常的邏輯是采用廣播的方式想集群中的所有節點發送消息,有點是集群中的數據同步較快,但是每條消息都需要發送給所有節點,對CPU和帶寬的消耗過大,所以
這里采用了gossip協議。
Gossip protocol 也叫 Epidemic Protocol(流行病協議),別名很多比如:“流言算法”、“疫情傳播算法”等。
它的特點是,在節點數量有限的網絡中,每個節點都會“隨機”(不是真正隨機,而是根據規則選擇通信節點)與部分節點通信,經過一番雜亂無章的通信后,每個節點的狀態在一定
時間內會達成一致,如下圖所示。
假設我們提前設置如下規則:
1、Gossip 是周期性的散播消息,把周期限定為 1 秒
2、被感染節點隨機選擇 k 個鄰接節點(fan-out)散播消息,這里把 fan-out 設置為 3,每次最多往 3 個節點散播。
3、每次散播消息都選擇尚未發送過的節點進行散播
4、收到消息的節點不再往發送節點散播,比如 A -> B,那么 B 進行散播的時候,不再發給 A。
這里一共有 16 個節點,節點 1 為初始被感染節點,通過 Gossip 過程,最終所有節點都被感染:
gossip協議包含多種消息,包括ping,pong,meet,fail等等。
ping:每個節點都會頻繁給其他節點發送ping,其中包含自己的狀態還有自己維護的集群元數據,互相通過ping交換元數據;
pong: 返回ping和meet,包含自己的狀態和其他信息,也可以用於信息廣播和更新;
fail: 某個節點判斷另一個節點fail之后,就發送fail給其他節點,通知其他節點,指定的節點宕機了。
meet:某個節點發送meet給新加入的節點,讓新節點加入集群中,然后新節點就會開始與其他節點進行通信,不需要發送形成網絡的所需的所有CLUSTER MEET命令。
發送CLUSTER MEET消息以便每個節點能夠達到其他每個節點只需通過一條已知的節點鏈就夠了。由於在心跳包中會交換gossip信息,將會創建節點間缺失的鏈接。
5、gossip的優缺點
優點: gossip協議的優點在於元數據的更新比較分散,不是集中在一個地方,更新請求會陸陸續續,打到所有節點上去更新有一定的延時,降低了壓力; 去中心化、可擴展、
容錯、一致性收斂、簡單。 由於不能保證某個時刻所有節點都收到消息,但是理論上最終所有節點都會收到消息,因此它是一個最終一致性協議。
缺點: 元數據更新有延時可能導致集群的一些操作會有一些滯后。 消息的延遲 , 消息冗余 。
參考資料
1、Redis高可用篇:主從數據同步原理
2、如何實現Redis高可用的
聲明: 公眾號如需轉載該篇文章,發表文章的頭部一定要 告知是轉至公眾號: 后端元宇宙。同時也可以問本人要markdown原稿和原圖片。其它情況一律禁止轉載!
