高可用HA(High Availability)是分布式系統架構設計中必須考慮的因素之一,它通常是指,通過設計減少系統不能提供服務的時間,
假設系統一直能夠提供服務,我們說系統的可用性是100%,如果系統每運行100個時間單位,會有1個時間單位無法提供服務,我們說系統的可用性是99%,很多公司的高可用目標是4個9,也就是99.99%,這就意味著,系統的年停機時間為8.76個小時,
那么如何保證系統的高可用呢
首先,在整個架構的每個節點中,不允許存在單點問題,因為單點一定是高可用最大的風險點,我們應該在系統設計程序中去避免單點問題,
在實作方法上,一般采用的就是集群部署、或者冗余部署來實作,這樣的設計使得如果某個節點出現故障,其他的節點還可以繼續使用,
Redis中高可用設計的必要性
Redis作為一個高性能Nosq中間件,會有很多熱點資料存放在Redis中,一旦Redis-server出現故障,會導致所有相關業務訪問都出現問題,另外,即便是設計了資料庫兜底的方案,大量請求對資料庫的訪問也很容易導致資料庫出現瓶頸,造成更大的災難,
除此之外,Redis的集群部署還可以帶來額外的收益:
- 負載(性能),Redis本身的QPS已經很高了,但是如果在一些并發量非常高的情況下,性能還是會受到影響,這個時候我們希望有更多的Redis服務來完成作業
- 擴容(水平擴展),第二個是出于存盤的考慮,因為Redis所有的資料都放在記憶體中,如果資料量大,很容易受到硬體的限制,升級硬體收效和成本比太低,所以我們需要有一種橫向擴展的方法
在Redis中,提供了高可用方案包含以下幾種:
- 主從復制(用來實作讀寫分離)
- 哨兵機制(實作master選舉)
- 集群機制(實作資料的分片)
Redis的Master-Slave方法
主從復制模式,簡單來說就是把一臺Redis服務器的資料,復制到其他Redis服務器中,其中負責復制資料的來源稱為master,被動接收資料并同步的節點稱為slave,資料的復制是單向的,如圖5-1所示,對于主從模式,其實有很多的變體,
在很多組件中都有使用這種思想,比如mysql的主從復制、redis的主從復制、activemq的主從復制、kafka里面的資料副本機制等等,所以大家需要舉一反三,融會貫通,

主從復制的好處
- 資料冗余,主從復制實作了資料的熱備,是除了持久化機制之外的另外一種資料冗余方式,
- 讀寫分離,使資料庫能支撐更大的并發,在報表中尤其重要,由于部分報表sql陳述句非常的慢,導致鎖表,影響前臺服務,如果前臺使用master,報表使用slave,那么報表sql將不會造成前臺鎖,保證了前臺速度,
- 負載均衡,在主從復制的基礎上,配合讀寫分離機制,可以由主節點提供寫服務,從節點提供服務,在讀多寫少的場景中,可以增加從節點來分擔redis-server讀操作的負載能力,從而大大提高redis-server的并發量
- 保證高可用,作為后備資料庫,如果主節點出現故障后,可以切換到從節點繼續作業,保證redis-server的高可用,
Redis如何配置主從復制
需要注意,Redis的主從復制,是直接在從節點發起就行,主節點不需要做任何事情
在Redis中有三種方式來開啟主從復制,
-
在從服務器的redis.conf組態檔中加入下面這個配置
replicaof <masterip> <masterport> -
通過啟動命令來配置,也就是啟動slave節點時執行如下命令
./redis-server ../redis.conf --replicaof <masterip> <masterport>
-
啟動redis-server之后,直接在客戶端視窗執行下面命令
redis>replicaof <masterip> <masterport>
準備三臺虛擬機
準備好三臺虛擬機,并且這三臺虛擬機需要能相互ping通,以及相互能夠訪問6379這個埠,如果訪問不了,需要關閉防火墻,
firewall-cmd --zone=public --add-port=6379/tcp --permanent
- 192.168.221.128(master)
- 192.168.221.129(slave)
- 192.168.221.130(slave)
這三臺機器上都需要安裝redis-server,安裝步驟如下,
注意事項,Redis6安裝需要gcc版本大于5.3以上,否則安裝會報錯,
# 升級到gcc 9.3:
yum -y install centos-release-scl
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
scl enable devtoolset-9 bash
# 需要注意的是scl命令啟用只是臨時的,退出shell或重啟就會恢復原系統gcc版本,
# 如果要長期使用gcc 9.3的話:
echo -e "\nsource /opt/rh/devtoolset-9/enable" >>/etc/profile
開始安裝
cd /usr/local/
wget http://download.redis.io/releases/redis-6.0.9.tar.gz
tar -zxvf redis-6.0.9.tar.gz
cd redis-6.0.9
make
make test
make install PREFIX=/data/program/redis
cp redis.conf /data/program/redis/redis.conf
演示配置程序
在192.168.221.129和192.168.221.130這兩臺機器上分別按照下面的操作增加配置,
-
編輯redis.conf檔案,通過shift+g跳轉到最后一行,增加如下配置
replicaof 192.168.221.128 6379 -
分別啟動這兩臺機器,啟動成功后,使用如下命令查看集群狀態
redis> info replication -
啟動日志中可以看到,在啟動程序中已經從master節點復制了資訊,
66267:S 12 Jul 2021 22:21:46.013 * Loading RDB produced by version 6.0.9
66267:S 12 Jul 2021 22:21:46.013 * RDB age 50 seconds
66267:S 12 Jul 2021 22:21:46.013 * RDB memory usage when created 0.77 Mb
66267:S 12 Jul 2021 22:21:46.013 * DB loaded from disk: 0.000 seconds
66267:S 12 Jul 2021 22:21:46.013 * Ready to accept connections
66267:S 12 Jul 2021 22:21:46.013 * Connecting to MASTER 192.168.221.128:6379
66267:S 12 Jul 2021 22:21:46.014 * MASTER <-> REPLICA sync started
66267:S 12 Jul 2021 22:21:46.015 * Non blocking connect for SYNC fired the event.
66267:S 12 Jul 2021 22:21:46.016 * Master replied to PING, replication can continue...
66267:S 12 Jul 2021 22:21:46.017 * Partial resynchronization not possible (no cached master)
66267:S 12 Jul 2021 22:21:46.039 * Full resync from master: acb74093b4c9d6fb527d3c713a44820ff0564508:0
66267:S 12 Jul 2021 22:21:46.058 * MASTER <-> REPLICA sync: receiving 188 bytes from master to disk
66267:S 12 Jul 2021 22:21:46.058 * MASTER <-> REPLICA sync: Flushing old data
66267:S 12 Jul 2021 22:21:46.058 * MASTER <-> REPLICA sync: Loading DB in memory
66267:S 12 Jul 2021 22:21:46.060 * Loading RDB produced by version 6.2.4
66267:S 12 Jul 2021 22:21:46.060 * RDB age 0 seconds
66267:S 12 Jul 2021 22:21:46.060 * RDB memory usage when created 1.83 Mb
66267:S 12 Jul 2021 22:21:46.060 * MASTER <-> REPLICA sync: Finished with success
如果沒有開啟日志,可以通過下面的方法進行開啟
- 找到Redis的組態檔 redis.conf
- 打開該組態檔, vi redis.conf;
- 通過linux的查詢命令找到 (loglevel下面)logfile " " ;
- 在冒號里面輸入日志的路徑,比如logfile “/usr/local/redis/log/redis.log”, 需要提前創建好目錄和檔案,redis默認不會創建該檔案,
接著,我們在master節點上通過設定一些key,會發現資料立刻就同步到了兩個slave節點上,從而完成了主從同步功能,不過在默認情況下,slave服務器是只讀的,如果直接在slave服務器上做修改,會報錯. 不過可以在slave服務器的redis.conf中找到一個屬性,允許slave服務器可以寫,但是不建議這么做,因為slave服務器上的更改不能往master上同步,會造成資料不同步的問題
slave-read-only no
Redis主從復制的原理分析
Redis的主從復制分兩種,一種是全量復制,另一種是增量復制,
全量復制
如圖5-2所示,表示Redis主從全量復制的整體時序圖,全量復制一般發生在Slave節點初始化階段,這個時候需要把master上所有資料都復制一份,具體步驟是:
-
從服務器連接主服務器,發送SYNC命令;
-
主服務器接收到SYNC命名后,開始執行BGSAVE命令生成RDB檔案并使用緩沖區記錄此后執行的所有寫命令;
-
主服務器BGSAVE執行完后,向所有從服務器發送快照檔案,并在發送期間繼續記錄被執行的寫命令(表示RDB異步生成快照期間的資料變更);
-
從服務器收到快照檔案后丟棄所有舊資料,載入收到的快照;
-
主服務器快照發送完畢后開始向從服務器發送緩沖區中的寫命令;
-
從服務器完成對快照的載入,開始接收命令請求,并執行來自主服務器緩沖區的寫命令;

問題:生成RDB期間,master接收到的命令怎么處理?
開始生成RDB檔案時,master會把所有新的寫命令快取在記憶體中,在 slave node 保存了RDB之后,再將新的寫命令復制給 slave node,(跟AOF重寫期間的思路是一樣的)
完成上面幾個步驟后就完成了slave服務器資料初始化的所有操作,savle服務器此時可以接收來自用戶的讀請求,同時,主從節點進入到命令傳播階段,在這個階段主節點將自己執行的寫命令發送給從節點,從節點接收命令并執行,從而保證主從節點資料的一致性,
在命令傳播階段,除了發送寫命令,主從節點還維持著心跳機制:PING和REPLCONF ACK,下面演示一下具體的實作,
-
在slave服務器redis cli上執行 REPLCONF listening-port 6379 (向主資料庫發送replconf命令說明自己的埠號)
-
開始同步,向master服務器發送sync命令開始同步,此時master會發送快照檔案和快取的命令,
127.0.0.1:6379> sync
Entering replica output mode... (press Ctrl-C to quit)
SYNC with master, discarding 202 bytes of bulk transfer...
SYNC done. Logging commands from master.
"ping"
"ping"
-
slave會將收到的內容寫入到硬碟上的臨時檔案,當寫入完成后會用該臨時檔案替換原有的RDB快照檔案,需要注意的是,在同步的程序中slave并不會阻塞,仍然可以處理客戶端的命令,默認情況下slave會用同步前的資料對命令進行回應,如果我們希望讀取的資料不能出現臟資料,那么可以在redis.conf檔案中配置下面的引數,來使得slave在同步完成對所有命令之前,都回復錯誤:SYNC with master in progress
slave-serve-stale-data no -
復制階段結束后,master執行的任何非查詢陳述句都會異步發送給slave, 可以在master節點執行set命令,可以在slave節點看到如下同步的指令,
redis > sync "set","11","11" "ping"
另外需要注意的是:
master/slave 復制策略是采用樂觀復制,也就是說可以容忍在一定時間內master/slave資料的內容是不同的,但是兩者的資料會最終同步成功,
具體來說,redis的主從同步程序本身是異步的,意味著master執行完客戶端請求的命令后會立即回傳結果給客戶端,然后異步的方式把命令同步給slave,這一特征保證啟用master/slave后 master的性能不會受到影響,
但是另一方面,如果在這個資料不一致的視窗期間,master/slave因為網路問題斷開連接,而這個時候,master是無法得知某個命令最終同步給了多少個slave資料庫,不過redis提供了一個配置項來限制只有資料至少同步給多少個slave的時候,master才是可寫的:
min-replicas-to-write 3 表示只有當3個或以上的slave連接到master,master才是可寫的
min-replicas-max-lag 10 表示允許slave最長失去連接的時間,如果10秒還沒收到slave的回應,則master認為該slave以斷開
修改master redis服務的redis.conf, 打開這兩個配置,重啟即可看到效果
增量復制
從Redis2.8開始,主從節點支持增量復制,并且是支持斷點續傳的增量復制,也就是說如果出現復制例外或者網路連接斷開導致復制中斷的情況,在系統恢復之后仍然可以按照上次復制的地方繼續同步,而不是全量復制,
它的具體原理是:主節點和從節點分別維護一個復制偏移量(offset),代表的是主節點向從節點傳遞的位元組數;主節點每次向從節點傳播N個位元組資料時,主節點的offset增加N;從節點每次收到主節點傳來的N個位元組資料時,從節點的offset增加N,主從節點的偏移量可以分別保存在:master_repl_offset:78130和slave_repl_offset這兩個欄位中,通過下面的命令可以查看,
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.221.128
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:77864
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:acb74093b4c9d6fb527d3c713a44820ff0564508
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:77864
second_repl_offset:-1
repl_backlog_active:1 # 開啟復制緩沖區
repl_backlog_size:1048576 # 緩沖區最大長度
repl_backlog_first_byte_offset:771 # 起始偏移量,計算當前快取區可用范圍
repl_backlog_histlen:77094 # 以保存資料的有效長度
無磁盤復制
前面我們說過,Redis復制的作業原理基于RDB方式的持久化實作的,也就是master在后臺保存RDB快照,slave接收到rdb檔案并載入,但是這種方式會存在一些問題,
-
當master禁用RDB時,如果執行了復制初始化操作,Redis依然會生成RDB快照,當master下次啟動時執行該RDB檔案的恢復,但是因為復制發生的時間點不確定,所以恢復的資料可能是任何時間點的,就會造成資料出現問題
-
當硬碟性能比較慢的情況下(網路硬碟),那初始化復制程序會對性能產生影響
因此2.8.18以后的版本,Redis引入了無硬碟復制選項,可以不需要通過RDB檔案去同步,直接發送資料,通過以下配置來開啟該功能:
repl-diskless-sync yes
master在記憶體中直接創建rdb,然后發送給slave,不會在自己本地落地磁盤了
主從復制注意事項
主從模式解決了資料備份和性能(通過讀寫分離)的問題,但是還是存在一些不足:
1、第一次建立復制的時候一定是全量復制,所以如果主節點資料量較大,那么復制延遲就比較長,此時應該盡量避開流量的高峰期,避免造成阻塞;如果有多個從節點需要建立對主節點的復制,可以考慮將幾個從節點錯開,避免主節點帶寬占用過大,此外,如果從節點過多,也可以調整主從復制的拓撲結構,由一主多從結構變為樹狀結構,
2、在一主一從或者一主多從的情況下,如果主服務器掛了,對外提供的服務就不可用了,單點問題沒有得到解決,如果每次都是手動把之前的從服務器切換成主服務器,這個比較費時費力,還會造成一定時間的服務不可用,
Master自動選舉之Sentinel哨兵機制
在前面講的master/slave模式,在一個典型的一主多從的系統中,slave在整個體系中起到了資料冗余備份和讀寫分離的作用,當master遇到例外終端后,開發者可以通過手動方式選擇一個slave資料庫來升級到master,使得系統能夠繼續提供服務,然后這個程序需要人工干預,比較麻煩; redis并沒有提供自動master選舉功能,而是需要借助一個哨兵來進行監控,
什么是哨兵
顧名思義,哨兵的作用就是監控Redis系統的運行狀況,它的功能包括兩個
-
監控master和slave是否正常運行
-
master出現故障時自動將slave資料庫升級為master
哨兵是一個獨立的行程,使用哨兵后的架構如圖5-3所示,同時為了保證哨兵的高可用,我們會對Sentinel做集群部署,因此Sentinel不僅僅監控Redis所有的主從節點,Sentinel也會實作相互監控,

配置哨兵集群
在前面主從復制的基礎上,增加三個sentinel節點,來實作對redis中master選舉的功能,
- 192.168.221.128(sentinel)
- 192.168.221.129(sentinel)
- 192.168.221.130(sentinel)
sentinel哨兵的配置方式如下:
- 從redis-6.0.9原始碼包中拷貝sentinel.conf檔案到redis/bin安裝目錄下
cp /data/program/redis-6.0.9/sentinel.conf /data/program/redis/sentinel.conf
- 修改以下配置
# 其中name表示要監控的master的名字,這個名字是自己定義,ip和port表示master的ip和埠號,最后一個2表示最低通過票數,也就是說至少需要幾個哨兵節點認為master下線才算是真的下線
sentinel monitor mymaster 192.168.221.128 6379 2
sentinel down-after-milliseconds mymaster 5000 # 表示如果5s內mymaster沒回應,就認為SDOWN
sentinel failover-timeout mymaster 15000 # 表示如果15秒后,mysater仍沒活過來,則啟動failover,從剩下的slave中選一個升級為master
logfile "/data/program/redis/logs/sentinels.log" # 需要提前創建好檔案
- 通過下面這個命令啟動sentinel哨兵
./redis-sentinel ../sentinel.conf
- 啟動成功后,得到一下資訊,表示哨兵啟動成功并且開始監控集群節點
103323:X 13 Jul 2021 15:16:28.624 # Sentinel ID is 2e9b0ac7ffbfca08e80debff744a4541a31b3951
103323:X 13 Jul 2021 15:16:28.624 # +monitor master mymaster 192.168.221.128 6379 quorum 2
103323:X 13 Jul 2021 15:16:28.627 * +slave slave 192.168.221.129:6379 192.168.221.129 6379 @ mymaster 192.168.221.128 6379
103323:X 13 Jul 2021 15:16:28.628 * +slave slave 192.168.221.130:6379 192.168.221.130 6379 @ mymaster 192.168.221.128 6379
103323:X 13 Jul 2021 15:16:48.765 * +fix-slave-config slave 192.168.221.130:6379 192.168.221.130 6379 @ mymaster 192.168.221.128 6379
103323:X 13 Jul 2021 15:16:48.765 * +fix-slave-config slave 192.168.221.129:6379 192.168.221.129 6379 @ mymaster 192.168.221.128 6379
其他兩個節點的配置和上面完全相同,都去監視master節點即可,主要,sentinel.conf檔案中master節點的ip一定不能輸127.0.0.1,否則其他sentinel節點無法和它通信
當其他sentinel哨兵節點啟動后,第一臺啟動的sentinel節點還會輸出如下日志,表示有其他sentinel節點加入進來,
+sentinel sentinel d760d62e190354654490e75e0b427d8ae095ac5a 192.168.221.129 26379 @ mymaster 192.168.221.128 6379
103323:X 13 Jul 2021 15:24:31.421
+sentinel sentinel dc6d874fe71e4f8f25e15946940f2b8eb087b2e8 192.168.221.130 26379 @ mymaster 192.168.221.128 6379
模擬master節點故障
我們直接把redis主從復制集群的master節點,通過./redis-cli shutdown命令停止,于是我們觀察三個sentinel哨兵的日志,先來看第一臺啟動的sentinel日志,得到如下內容,
103625:X 13 Jul 2021 15:35:01.241 # +new-epoch 9
103625:X 13 Jul 2021 15:35:01.244 # +vote-for-leader d760d62e190354654490e75e0b427d8ae095ac5a 9
103625:X 13 Jul 2021 15:35:01.267 # +odown master mymaster 192.168.221.128 6379 #quorum 2/2
103625:X 13 Jul 2021 15:35:01.267 # Next failover delay: I will not start a failover before Tue Jul 13 15:35:31 2021
103625:X 13 Jul 2021 15:35:02.113 # +config-update-from sentinel d760d62e190354654490e75e0b427d8ae095ac5a 192.168.221.129 26379 @ mymaster 192.168.221.128 6379
103625:X 13 Jul 2021 15:35:02.113 # +switch-master mymaster 192.168.221.128 6379 192.168.221.130 6379
103625:X 13 Jul 2021 15:35:02.113 * +slave slave 192.168.221.129:6379 192.168.221.129 6379 @ mymaster 192.168.221.130 6379
103625:X 13 Jul 2021 15:35:02.113 * +slave slave 192.168.221.128:6379 192.168.221.128 6379 @ mymaster 192.168.221.130 6379
103625:X 13 Jul 2021 15:35:07.153 # +sdown slave 192.168.221.128:6379 192.168.221.128 6379 @ mymaster 192.168.221.130 6379
+sdown表示哨兵主觀認為master已經停止服務了,
+odown表示哨兵客觀認為master停止服務了(關于主觀和客觀,后面會給大家講解),
接著哨兵開始進行故障恢復,挑選一個slave升級為master,其他哨兵節點的日志,
76274:X 13 Jul 2021 15:35:01.240 # +try-failover master mymaster 192.168.221.128 6379
76274:X 13 Jul 2021 15:35:01.242 # +vote-for-leader d760d62e190354654490e75e0b427d8ae095ac5a 9
76274:X 13 Jul 2021 15:35:01.242 # d760d62e190354654490e75e0b427d8ae095ac5a voted for d760d62e190354654490e75e0b427d8ae095ac5a 9
76274:X 13 Jul 2021 15:35:01.247 # dc6d874fe71e4f8f25e15946940f2b8eb087b2e8 voted for d760d62e190354654490e75e0b427d8ae095ac5a 9
76274:X 13 Jul 2021 15:35:01.247 # 2e9b0ac7ffbfca08e80debff744a4541a31b3951 voted for d760d62e190354654490e75e0b427d8ae095ac5a 9
76274:X 13 Jul 2021 15:35:01.309 # +elected-leader master mymaster 192.168.221.128 6379
76274:X 13 Jul 2021 15:35:01.309 # +failover-state-select-slave master mymaster 192.168.221.128 6379
76274:X 13 Jul 2021 15:35:01.400 # +selected-slave slave 192.168.221.130:6379 192.168.221.130 6379 @ mymaster 192.168.221.128 6379
76274:X 13 Jul 2021 15:35:01.400 * +failover-state-send-slaveof-noone slave 192.168.221.130:6379 192.168.221.130 6379 @ mymaster 192.168.221.128 6379
76274:X 13 Jul 2021 15:35:01.477 * +failover-state-wait-promotion slave 192.168.221.130:6379 192.168.221.130 6379 @ mymaster 192.168.221.128 6379
76274:X 13 Jul 2021 15:35:02.045 # +promoted-slave slave 192.168.221.130:6379 192.168.221.130 6379 @ mymaster 192.168.221.128 6379
76274:X 13 Jul 2021 15:35:02.045 # +failover-state-reconf-slaves master mymaster 192.168.221.128 6379
76274:X 13 Jul 2021 15:35:02.115 * +slave-reconf-sent slave 192.168.221.129:6379 192.168.221.129 6379 @ mymaster 192.168.221.128 6379
76274:X 13 Jul 2021 15:35:03.070 * +slave-reconf-inprog slave 192.168.221.129:6379 192.168.221.129 6379 @ mymaster 192.168.221.128 6379
76274:X 13 Jul 2021 15:35:03.070 * +slave-reconf-done slave 192.168.221.129:6379 192.168.221.129 6379 @ mymaster 192.168.221.128 6379
76274:X 13 Jul 2021 15:35:03.133 # +failover-end master mymaster 192.168.221.128 6379
76274:X 13 Jul 2021 15:35:03.133 # +switch-master mymaster 192.168.221.128 6379 192.168.221.130 6379
76274:X 13 Jul 2021 15:35:03.133 * +slave slave 192.168.221.129:6379 192.168.221.129 6379 @ mymaster 192.168.221.130 6379
76274:X 13 Jul 2021 15:35:03.133 * +slave slave 192.168.221.128:6379 192.168.221.128 6379 @ mymaster 192.168.221.130 6379
76274:X 13 Jul 2021 15:35:08.165 # +sdown slave 192.168.221.128:6379 192.168.221.128 6379 @ mymaster 192.168.221.130 6379
+try-failover表示哨兵開始進行故障恢復
+failover-end 表示哨兵完成故障恢復
+slave表示列出新的master和slave服務器,我們仍然可以看到已經停掉的master,哨兵并沒有清除已停止的服務的實體,這是因為已經停止的服務器有可能會在某個時間進行恢復,恢復以后會以slave角色加入到整個集群中,
實作原理
1):每個Sentinel以每秒鐘一次的頻率向它所知的Master/Slave以及其他 Sentinel 實體發送一個 PING 命令
2):如果一個實體(instance)距離最后一次有效回復 PING 命令的時間超過 down-after-milliseconds 選項所指定的值, 則這個實體會被 Sentinel 標記為主觀下線,
3):如果一個Master被標記為主觀下線,則正在監視這個Master的所有 Sentinel 要以每秒一次的頻率確認Master的確進入了主觀下線狀態,
4):當有足夠數量的 Sentinel(大于等于組態檔指定的值:quorum)在指定的時間范圍內確認Master的確進入了主觀下線狀態, 則Master會被標記為客觀下線 ,
5):在一般情況下, 每個 Sentinel 會以每 10 秒一次的頻率向它已知的所有Master,Slave發送 INFO 命令
6):當Master被 Sentinel 標記為客觀下線時,Sentinel 向下線的 Master 的所有 Slave 發送 INFO 命令的頻率會從 10 秒一次改為每秒一次 ,若沒有足夠數量的 Sentinel 同意 Master 已經下線, Master 的客觀下線狀態就會被移除,
8):若 Master 重新向 Sentinel 的 PING 命令回傳有效回復, Master 的主觀下線狀態就會被移除,
主觀下線:Subjectively Down,簡稱 SDOWN,指的是當前 Sentinel 實體對某個redis服務器做出的下線判斷,
客觀下線:Objectively Down, 簡稱 ODOWN,指的是多個 Sentinel 實體在對Master Server做出 SDOWN 判斷,并且通過 SENTINEL之間交流后得出Master下線的判斷,然后開啟failover
誰來完成故障轉移?
當redis中的master節點被判定為客觀下線之后,需要重新從slave節點選擇一個作為新的master節點,那現在有三個sentinel節點,應該由誰來完成這個故障轉移程序呢?所以這三個sentinel節點必須要通過某種機制達成一致,在Redis中采用了Raft演算法來實作這個功能,
每次master出現故障時,都會觸發raft演算法來選擇一個leader完成redis主從集群中的master選舉功能,
資料一致性問題
了解raft演算法之前,我們來了解一個拜占庭將軍問題,
拜占庭將軍問題(Byzantine failures),是由萊斯利·蘭伯特提出的點對點通信中的基本問題,具體含義是在存在訊息丟失的不可靠信道上試圖通過訊息傳遞的方式達到一致性是不可能的,
拜占庭位于如今的土耳其的伊斯坦布爾,是東羅馬帝國的首都,由于當時拜占庭羅馬帝國國土遼闊,為了達到防御目的,每個軍隊都分隔很遠,將軍與將軍之間只能靠信差傳訊息,在戰爭的時候,拜占庭軍隊內所有將軍和副官必須達成一致的共識,決定是否有贏的機會才去攻打敵人的陣營,但是,在軍隊內有可能存有叛徒和敵軍的間諜,左右將軍們的決定又擾亂整體軍隊的秩序,在進行共識時,結果并不代表大多數人的意見,這時候,在已知有成員謀反的情況下,其余忠誠的將軍在不受叛徒的影響下如何達成一致的協議,這就是是著名的拜占庭問題,
拜占庭將軍問題本質上所描述的是計算機領域中的一個協議問題,拜占庭帝國軍隊的將軍們必須全體一致的決定是否攻擊某一支敵軍,問題是這些將軍在地理上是分隔開來的,并且將軍中存在叛徒,叛徒可以任意行動以達到以下目標:
- 欺騙某些將軍采取進攻行動;
- 促成一個不是所有將軍都同意的決定,如當將軍們不希望進攻時促成進攻行動;
- 或者迷惑某些將軍,使他們無法做出決定,
如果叛徒達到了這些目的之一,則任何攻擊行動的結果都是注定要失敗的,只有完全達成一致的努力才能獲得勝利 ,
拜占庭假設是對現實世界的模型化,由于硬體錯誤、網路擁塞或斷開以及遭到惡意攻擊,計算機和網路可能出現不可預料的行為,所以如何在這樣的環境下達成一致,這就是所謂的資料一致性問題,
回到Sentinel中,這三個Sentinel節點,需要選擇一個節點來負責針對redis集群進行故障恢復,那這三個節點中誰能做這個事情?因此同樣需要基于某個機制來達成共識,
在很多中間件中都需要用到資料一致性演算法,最直觀的是像zookeeper這樣一個組件,他的高可用設計是由leader和follow組成,當leader節點因為例外宕機了, 需要從生下的follow節點選舉出一個新的leader節點,那么這個選舉程序需要集群中所有節點達成一致,也就是只有所有節點都贊同某個follow節點成為leader,它才能成為leader節點,而這個共識達成的前提是所有節點需要對某個投票結果達成一致,否則就無法選舉出新的leader,因此這里必然需要用到共識演算法,
常見的資料一致性演算法
- paxos,paxos應該是最早也是最正統的資料一致性演算法,也是最復雜難懂的演算法,
- raft,raft演算法應該是最通俗易懂的一致性演算法,它在nacos、sentinel、consul等組件中都有使用,
- zab協議,是zookeeper中基于paxos演算法上演變過來的一種一致性演算法
- distro,Distro協議,Distro是阿里巴巴的私有協議,目前流行的Nacos服務管理框架就采用了Distro協議,Distro 協議被定位為 臨時資料的一致性協議
Raft協議說明
Raft演算法影片演示地址: http://thesecretlivesofdata.com/raft/
Raft演算法的核心思想:先到先得,少數服從多數,
故障轉移程序
怎么讓一個原來的slave節點成為主節點?
-
選出Sentinel Leader之后,由Sentinel Leader向某個節點發送slaveof no one命令,讓它成為獨立節點,
-
然后向其他節點發送replicaof x.x.x.x xxxx(本機服務),讓它們成為這個節點的子節點,故障轉移完成,
如何選擇合適的slave節點成為master呢?有四個因素影響,
- 斷開連接時長,如果與哨兵連接斷開的比較久,超過了某個閾值,就直接失去了選舉權
- 優先級排序,如果擁有選舉權,那就看誰的優先級高,這個在組態檔里可以設定(replica-priority 100),數值越小優先級越高
- 復制數量,如果優先級相同,就看誰從master中復制的資料最多(復制偏移量最大)
- 行程id,如果復制數量也相同,就選擇行程id最小的那個
Sentinel功能總結
監控:Sentinel會不斷檢查主服務器和從服務器是否正常運行,
通知:如果某一個被監控的實體出現問題,Sentinel可以通過API發出通知,
自動故障轉移(failover):如果主服務器發生故障,Sentinel可以啟動故障轉移程序,把某臺服務器升級為主服務器,并發出通知,
配置管理:客戶端連接到Sentinel,獲取當前的Redis主服務器的地址,
Redis分布式擴展之Redis Cluster方案
主從切換的程序中會丟失資料,因為只有一個master,只能單點寫,沒有解決水平擴容的問題,而且每個節點都保存了所有資料,一個是記憶體的占用率較高,另外就是如果進行資料恢復時,非常慢,而且資料量過大對資料IO操作的性能也會有影響,
所以我們同樣也有對Redis資料分片的需求,所謂分片就是把一份大資料拆分成多份小資料,在3.0之前,我們只能通過構建多個redis主從節點集群,把不同業務資料拆分到不冉的集群中,這種方式在業務層需要有大量的代碼來完成資料分片、路由等作業,導致維護成本高、增加、移除節點比較繁瑣,
Redis3.0之后引入了Redis Cluster集群方案,它用來解決分布式擴展的需求,同時也實作了高可用機制,
Redis Cluster架構
一個Redis Cluster由多個Redis節點構成,不同節點組服務的資料沒有交集,也就是每個一節點組對應資料sharding的一個分片,
節點組內部分為主備兩類節點,對應master和slave節點,兩者資料準實時一致,通過異步化訂的主備復制機制來保證,
一個節點組有且只有一個master節點,同時可以有0到多個slave節點,在這個節點組中只有master節點對用戶提供些服務,讀服務可以由master或者slave提供,如圖5-4中,包含三個master節點以及三個master對應的slave節點,一般一組集群至少要6個節點才能保證完整的高可用,
其中三個master會分配不同的slot(表示資料分片區間),當master出現故障時,slave會自動選舉成為master頂替主節點繼續提供服務,

關于gossip協議
在圖5-4描述的架構中,其他的點都好理解,就是關于gossip協議是干嘛的,需要單獨說明一下,
在整個redis cluster架構中,如果出現以下情況
- 新加入節點
- slot遷移
- 節點宕機
- slave選舉成為master
我們希望這些變化能夠讓整個集群中的每個節點都能夠盡快發現,傳播到整個集群并且集群中所有節點達成一致,那么各個節點之間就需要相互連通并且攜帶相關狀態資料進行傳播,
按照正常的邏輯是采用廣播的方式想集群中的所有節點發送訊息,有點是集群中的資料同步較快,但是每條訊息都需要發送給所有節點,對CPU和帶寬的消耗過大,所以這里采用了gossip協議,
Gossip protocol 也叫 Epidemic Protocol (流行病協議),別名很多比如:“流言演算法”、“疫情傳播演算法”等,
它的特點是,在節點數量有限的網路中,每個節點都會“隨機”(不是真正隨機,而是根據規則選擇通信節點)與部分節點通信,經過一番雜亂無章的通信后,每個節點的狀態在一定時間內會達成一致,如圖5-5所示,
假設我們提前設定如下規則:
1、Gossip 是周期性的散播訊息,把周期限定為 1 秒
2、被感染節點隨機選擇 k 個鄰接節點(fan-out)散播訊息,這里把 fan-out 設定為 3,每次最多往 3 個節點散播,
3、每次散播訊息都選擇尚未發送過的節點進行散播
4、收到訊息的節點不再往發送節點散播,比如 A -> B,那么 B 進行散播的時候,不再發給 A,
這里一共有 16 個節點,節點 1 為初始被感染節點,通過 Gossip 程序,最終所有節點都被感染:

gossip協議訊息
gossip協議包含多種訊息,包括ping,pong,meet,fail等等,
ping:每個節點都會頻繁給其他節點發送ping,其中包含自己的狀態還有自己維護的集群元資料,互相通過ping交換元資料;
pong: 回傳ping和meet,包含自己的狀態和其他資訊,也可以用于資訊廣播和更新;
fail: 某個節點判斷另一個節點fail之后,就發送fail給其他節點,通知其他節點,指定的節點宕機了,
meet:某個節點發送meet給新加入的節點,讓新節點加入集群中,然后新節點就會開始與其他節點進行通信,不需要發送形成網路的所需的所有CLUSTER MEET命令,發送CLUSTER MEET訊息以便每個節點能夠達到其他每個節點只需通過一條已知的節點鏈就夠了,由于在心跳包中會交換gossip資訊,將會創建節點間缺失的鏈接,
gossip的優缺點
優點: gossip協議的優點在于元資料的更新比較分散,不是集中在一個地方,更新請求會陸陸續續,打到所有節點上去更新有一定的延時,降低了壓力; 去中心化、可擴展、容錯、一致性收斂、簡單, 由于不能保證某個時刻所有節點都收到訊息,但是理論上最終所有節點都會收到訊息,因此它是一個最終一致性協議,
缺點: 元資料更新有延時可能導致集群的一些操作會有一些滯后, 訊息的延遲 , 訊息冗余 ,
Redis Cluster集群搭建
集群至少需要6個節點(3主3從模式),每一個節點可以搭建在同一臺機器上,也可以搭建在不同的服務器上,
-
192.168.221.128 7000 、 7001
-
192.168.221.129 7002 、 7003
-
192.168.221.130 7004 、 7005
分別啟動6個節點,
- 在redis安裝目錄下,分別創建以下目錄,這些目錄必須要提前創建好,redis啟動時不會主動創建這些目錄,
mkdir -p /data/program/redis/run
mkdir -p /data/program/redis/logs
mkdir -p /data/program/redis/data/7000、7001
mkdir -p /data/program/redis/conf
mkdir -p /data/program/redis/redis-cluster
- 拷貝一份redis.conf到redis-cluster目錄下,由于只有三臺機器,所以每個機器上需要運行兩個redis-server,因此需要修改redis.conf檔案的名字來做區分,redis_7000.conf,并且修改該檔案的一下內容,
pidfile "/data/program/redis/run/redis_7000.pid" #pid存盤目錄
logfile "/data/program/redis/logs/redis_7000.log" #日志存盤目錄
dir "/data/program/redis/data/7000" #資料存盤目錄,目錄要提前創建好
cluster-enabled yes #開啟集群
cluster-config-file nodes-7000.conf #集群節點組態檔,這個檔案是不能手動編輯的,確保每一個集群節點的組態檔不同
cluster-node-timeout 15000 #集群節點的超時時間,單位:ms,超時后集群會認為該節點失敗
- 每個節點需要啟動兩個redis-server,所以對組態檔做一份拷貝,然后修改以下配置
pidfile "/data/program/redis/run/redis_7001.pid" #pid存盤目錄
logfile "/data/program/redis/logs/redis_7001.log" #日志存盤目錄
dir "/data/program/redis/data/7001" #資料存盤目錄,目錄要提前創建好
cluster-enabled yes #開啟集群
cluster-config-file nodes-7001.conf #集群節點組態檔,這個檔案是不能手動編輯的,確保每一個集群節點的組態檔不同
cluster-node-timeout 15000 #集群節點的超時時間,單位:ms,超時后集群會認為該節點失敗
-
創建兩個腳本用來進行統一的服務運行
cluster-start.sh
./redis-server ../conf/redis_7000.conf
./redis-server ../conf/redis_7001.conf
cluster-shutdown.sh
pgrep redis-server | xargs -exec kill -9
通過下面命令讓上述腳本擁有執行權限
chmod +x cluster-*.sh
- 其他兩個節點重復上述的程序,完成6個節點的啟動,
配置redis 集群
啟動完這5臺服務器后,需要通過下面的操作來配置集群節點,在redis6.0版本中,創建集群的方式為redis-cli方式直接創建,以下命令在任意一臺服務器上執行即可
用以下命令創建集群,--cluster-replicas 1 引數表示希望每個主服務器都有一個從服務器,這里則代表3主3從,通過該方式創建的帶有從節點的機器不能夠自己手動指定主節點,redis集群會盡量把主從服務器分配在不同機器上
[root@localhost bin]# ./redis-cli --cluster create 192.168.221.128:7000 192.168.221.128:7001 192.168.221.129 7002 192.168.221.129 7003 192.168.221.130 7004 192.168.221.130 7005 --cluster-replicas 1
執行上述命令后,會得到以下執行結果,
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 192.168.221.129:7003 to 192.168.221.128:7000
Adding replica 192.168.221.130:7005 to 192.168.221.129:7002
Adding replica 192.168.221.128:7001 to 192.168.221.130:7004
M: 36d34fd3985179786eeab50338e3972608df2f21 192.168.221.128:7000 #master
slots:[0-5460] (5461 slots) master
S: 202028cfaf69fd5c8fcd5b7b75677d6963184ad9 192.168.221.128:7001
replicates 124683446267c8910cd080238e72e3b1b589f41f
M: 5927296015093b9474fed5a354c4a04b9345e7a9 192.168.221.129:7002 #master
slots:[5461-10922] (5462 slots) master
S: 089b77cb753c1ef62bd10f23230c38d4a0a64a09 192.168.221.129:7003
replicates 36d34fd3985179786eeab50338e3972608df2f21
M: 124683446267c8910cd080238e72e3b1b589f41f 192.168.221.130:7004 #master
slots:[10923-16383] (5461 slots) master
S: 82a9fe027179f197ff82547863c4252de8ba1354 192.168.221.130:7005
replicates 5927296015093b9474fed5a354c4a04b9345e7a9
Can I set the above configuration? (type 'yes' to accept): yes
從上述結果中看到兩個點:
- 預先分配三個節點的slot區間
- 自動選擇合適的節點作為master
查看集群狀態等資訊
- cluster info 查看集群狀態資訊
[root@localhost bin]# ./redis-cli -p 7000
127.0.0.1:7000> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:276
cluster_stats_messages_pong_sent:262
cluster_stats_messages_sent:538
cluster_stats_messages_ping_received:257
cluster_stats_messages_pong_received:276
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:538
- 查看集群節點資訊
127.0.0.1:7000> cluster nodes
5927296015093b9474fed5a354c4a04b9345e7a9 192.168.221.129:7002@17002 master - 0 1626247374000 3 connected 5461-10922
36d34fd3985179786eeab50338e3972608df2f21 192.168.221.128:7000@17000 myself,master - 0 1626247375000 1 connected 0-5460
82a9fe027179f197ff82547863c4252de8ba1354 192.168.221.130:7005@17005 slave 5927296015093b9474fed5a354c4a04b9345e7a9 0 1626247376000 3 connected
089b77cb753c1ef62bd10f23230c38d4a0a64a09 192.168.221.129:7003@17003 slave 36d34fd3985179786eeab50338e3972608df2f21 0 1626247375000 1 connected
124683446267c8910cd080238e72e3b1b589f41f 192.168.221.130:7004@17004 master - 0 1626247376830 5 connected 10923-16383
202028cfaf69fd5c8fcd5b7b75677d6963184ad9 192.168.221.128:7001@17001 slave 124683446267c8910cd080238e72e3b1b589f41f 0 1626247375000 5 connected
資料分布
Redis Cluster中,Sharding采用slot(槽)的概念,一共分成16384個槽,這有點兒類似pre sharding思路,對于每個進入Redis的鍵值對,根據key進行散列,分配到這16384個slot中的某一個中,使用的hash演算法也比較簡單,就是CRC16后16384取模[crc16(key)%16384],
Redis集群中的每個node(節點)負責分攤這16384個slot中的一部分,也就是說,每個slot都對應一個node負責處理,
如圖5-6所示,假設現在我們是三個主節點分別是:A, B, C 三個節點,它們可以是一臺機器上的三個埠,也可以是三臺不同的服務器,那么,采用哈希槽 (hash slot)的方式來分配16384個slot 的話,它們三個節點分別承擔的slot 區間是:
-
節點A覆寫0-5000;
-
節點B覆寫5001-10000;
-
節點C覆寫10001-16383

客戶端重定向
如圖5-6所示,假設k這個key應該存盤在node3上,而此時用戶在node1或者node2上呼叫set k v指令,這個時候redis cluster怎么處理呢?
127.0.0.1:7291> set qs 1
(error) MOVED 13724 127.0.0.1:7293
服務端回傳MOVED,也就是根據key計算出來的slot不歸當前節點管理,服務端回傳MOVED告訴客戶端去7293埠操作,
這個時候更換埠,用redis-cli –p 7293操作,才會回傳OK,或者用./redis-cli -c -p port的命令,但是導致的問題是,客戶端需要連接兩次才能完成操作,所以大部分的redis客戶端都會在本地維護一份slot和node的對應關系,在執行指令之前先計算當前key應該存盤的目標節點,然后再連接到目標節點進行資料操作,
在redis集群中提供了下面的命令來計算當前key應該屬于哪個slot
redis> cluster keyslot key1
高可用主從切換原理
如果主節點沒有從節點,那么當它發生故障時,集群就將處于不可用狀態,
一旦某個master節點進入到FAIL狀態,那么整個集群都會變成FAIL狀態,同時觸發failover機制,failover的目的是從slave節點中選舉出新的主節點,使得集群可以恢復正常,這個程序實作如下:
當slave發現自己的master變為FAIL狀態時,便嘗試進行Failover,以期成為新的master,由于掛掉的master可能會有多個slave,從而存在多個slave競爭成為master節點的程序, 其程序如下:
- slave發現自己的master變為FAIL
- 將自己記錄的集群currentEpoch加1,并廣播FAILOVER_AUTH_REQUEST 資訊
- 其他節點收到該資訊,只有master回應,判斷請求者的合法性,并發送FAILOVER_AUTH_ACK,對每一個epoch只發送一次ack
- 嘗試failover的slave收集master回傳的FAILOVER_AUTH_ACK
- slave收到超過半數master的ack后變成新Master (這里解釋了集群為什么至少需要三個主節點,如果只有兩個,當其中一個掛了,只剩一個主節點是不能選舉成功的)
- 廣播Pong訊息通知其他集群節點,
從節點并不是在主節點一進入 FAIL 狀態就馬上嘗試發起選舉,而是有一定延遲,一定的延遲確保我們等待FAIL狀態在集群中傳播,slave如果立即嘗試選舉,其它masters或許尚未意識到FAIL狀態,可能會拒絕投票,
延遲計算公式: DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms
SLAVE_RANK表示此slave已經從master復制資料的總量的rank,Rank越小代表已復制的資料越新,這種方式下,持有最新資料的slave將會首先發起選舉
常見問題分析
問題:怎么讓相關的資料落到同一個節點上?
比如有些multi key操作是不能跨節點的,例如用戶2673的基本資訊和金融資訊?
在key里面加入{hash tag}即可,Redis在計算槽編號的時候只會獲取{}之間的字串進行槽編號計算,這樣由于上面兩個不同的鍵,{}里面的字串是相同的,因此他們可以被計算出相同的槽
user{2673}base=…
user{2673}fin=…
操作步驟如下,下面這些key都會保存到同一個node中,
127.0.0.1:7293> set a{qs}a 1
OK
127.0.0.1:7293> set a{qs}b 1
OK
127.0.0.1:7293> set a{qs}c 1
OK
127.0.0.1:7293> set a{qs}d 1
OK
127.0.0.1:7293> set a{qs}e 1
總結
優勢
-
無中心架構,
-
資料按照slot存盤分布在多個節點,節點間資料共享,可動態調整資料分布,
-
可擴展性,可線性擴展到1000個節點(官方推薦不超過1000個),節點可動態添加或洗掉,
-
高可用性,部分節點不可用時,集群仍可用,通過增加Slave做standby資料副本,能夠實作故障自動failover,節點之間通過gossip協議交換狀態資訊,用投票機制完成Slave到Master的角色提升,
-
降低運維成本,提高系統的擴展性和可用性,
不足
- Client實作復雜,驅動要求實作Smart Client,快取slots mapping資訊并及時更新,提高了開發難度,客戶端的不成熟影響業務的穩定性,
- 節點會因為某些原因發生阻塞(阻塞時間大于clutser-node-timeout),被判斷下線,這種failover是沒有必要的,
- 資料通過異步復制,不保證資料的強一致性,
- 多個業務使用同一套集群時,無法根據統計區分冷熱資料,資源隔離性較差,容易出現相互影響的情況,
- Slave在集群中充當“冷備”,不能緩解讀壓力,當然可以通過SDK的合理設計來提高Slave資源的利用率,
Redission連接cluster
修改redisson.yml檔案,參考spring-boot-redis-client-example這個專案
clusterServersConfig:
nodeAddresses:
- "redis://192.168.221.129:7003"
- "redis://192.168.221.129:7002"
- "redis://192.168.221.130:7004"
codec: !<org.redisson.codec.JsonJacksonCodec> {}
注意,nodeAddresses對應的節點都是master,
Codis
Codis 是一個分布式 Redis 解決方案, 對于上層的應用來說, 連接到 Codis Proxy 和連接原生的 Redis Server 沒有明顯的區別(不支持的命令串列), 上層應用可以像使用單機的 Redis 一樣使用, Codis 底層會處理請求的轉發, 不停機的資料遷移等作業,
所有后邊的一切事情, 對于前面的客戶端來說是透明的, 可以簡單的認為后邊連接的是一個記憶體無限大的 Redis 服務,
codis的架構
如圖5-7所示,表示Codis的整體架構圖,
Codis Proxy: 客戶端連接的 Redis 代理服務, 實作了 Redis 協議, 除部分命令不支持以外(不支持的命令串列),表現的和原生的 Redis 沒有區別(就像 Twemproxy),對于同一個業務集群而言,可以同時部署多個 codis-proxy 實體;不同 codis-proxy 之間由 codis-dashboard 保證狀態同
codis-redis-group: 代表一個redis服務集群節點,一個RedisGroup里有一個Master,和多個Slave
Zookeeper:Codis 依賴 ZooKeeper 來存放資料路由表和 codis-proxy 節點的元資訊, codis-config 發起的命令都會通過 ZooKeeper 同步到各個存活的 codis-proxy.


轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/333252.html
標籤:其他
