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

<center>圖5-1</center>
主從復(fù)制的好處
- 數(shù)據(jù)冗余,主從復(fù)制實(shí)現(xiàn)了數(shù)據(jù)的熱備,是除了持久化機(jī)制之外的另外一種數(shù)據(jù)冗余方式。
- 讀寫分離,使數(shù)據(jù)庫能支撐更大的并發(fā)。在報(bào)表中尤其重要。由于部分報(bào)表sql語句非常的慢,導(dǎo)致鎖表,影響前臺服務(wù)。如果前臺使用master,報(bào)表使用slave,那么報(bào)表sql將不會造成前臺鎖,保證了前臺速度。
- 負(fù)載均衡,在主從復(fù)制的基礎(chǔ)上,配合讀寫分離機(jī)制,可以由主節(jié)點(diǎn)提供寫服務(wù),從節(jié)點(diǎn)提供服務(wù)。在讀多寫少的場景中,可以增加從節(jié)點(diǎn)來分擔(dān)redis-server讀操作的負(fù)載能力,從而大大提高redis-server的并發(fā)量
- 保證高可用,作為后備數(shù)據(jù)庫,如果主節(jié)點(diǎn)出現(xiàn)故障后,可以切換到從節(jié)點(diǎn)繼續(xù)工作,保證redis-server的高可用。
Redis如何配置主從復(fù)制
需要注意,Redis的主從復(fù)制,是直接在從節(jié)點(diǎn)發(fā)起就行,主節(jié)點(diǎn)不需要做任何事情
在Redis中有三種方式來開啟主從復(fù)制。
-
在從服務(wù)器的redis.conf配置文件中加入下面這個配置
replicaof <masterip> <masterport> 通過啟動命令來配置,也就是啟動slave節(jié)點(diǎn)時執(zhí)行如下命令
./redis-server ../redis.conf --replicaof <masterip> <masterport>
-
啟動redis-server之后,直接在客戶端窗口執(zhí)行下面命令
redis>replicaof <masterip> <masterport>
準(zhǔn)備三臺虛擬機(jī)
準(zhǔn)備好三臺虛擬機(jī),并且這三臺虛擬機(jī)需要能相互ping通,以及相互能夠訪問6379這個端口,如果訪問不了,需要關(guān)閉防火墻。
firewall-cmd --zone=public --add-port=6379/tcp --permanent
- 192.168.221.128(master)
- 192.168.221.129(slave)
- 192.168.221.130(slave)
這三臺機(jī)器上都需要安裝redis-server,安裝步驟如下。
注意事項(xiàng),Redis6安裝需要gcc版本大于5.3以上,否則安裝會報(bào)錯。
# 升級到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或重啟就會恢復(fù)原系統(tǒng)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這兩臺機(jī)器上分別按照下面的操作增加配置。
-
編輯redis.conf文件,通過shift+g跳轉(zhuǎn)到最后一行,增加如下配置
replicaof 192.168.221.128 6379 -
分別啟動這兩臺機(jī)器,啟動成功后,使用如下命令查看集群狀態(tài)
redis> info replication 啟動日志中可以看到,在啟動過程中已經(jīng)從master節(jié)點(diǎn)復(fù)制了信息。
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
如果沒有開啟日志,可以通過下面的方法進(jìn)行開啟
- 找到Redis的配置文件 redis.conf
- 打開該配置文件, vi redis.conf;
- 通過linux的查詢命令找到 (loglevel下面)logfile " " ;
- 在冒號里面輸入日志的路徑,比如logfile “/usr/local/redis/log/redis.log”, 需要提前創(chuàng)建好目錄和文件,redis默認(rèn)不會創(chuàng)建該文件。
接著,我們在master節(jié)點(diǎn)上通過設(shè)置一些key,會發(fā)現(xiàn)數(shù)據(jù)立刻就同步到了兩個slave節(jié)點(diǎn)上,從而完成了主從同步功能。不過在默認(rèn)情況下,slave服務(wù)器是只讀的,如果直接在slave服務(wù)器上做修改,會報(bào)錯. 不過可以在slave服務(wù)器的redis.conf中找到一個屬性,允許slave服務(wù)器可以寫,但是不建議這么做。因?yàn)閟lave服務(wù)器上的更改不能往master上同步,會造成數(shù)據(jù)不同步的問題
slave-read-only no
Redis主從復(fù)制的原理分析
Redis的主從復(fù)制分兩種,一種是全量復(fù)制,另一種是增量復(fù)制。
全量復(fù)制
如圖5-2所示,表示Redis主從全量復(fù)制的整體時序圖,全量復(fù)制一般發(fā)生在Slave節(jié)點(diǎn)初始化階段,這個時候需要把master上所有數(shù)據(jù)都復(fù)制一份,具體步驟是:
從服務(wù)器連接主服務(wù)器,發(fā)送SYNC命令;
主服務(wù)器接收到SYNC命名后,開始執(zhí)行BGSAVE命令生成RDB文件并使用緩沖區(qū)記錄此后執(zhí)行的所有寫命令;
主服務(wù)器BGSAVE執(zhí)行完后,向所有從服務(wù)器發(fā)送快照文件,并在發(fā)送期間繼續(xù)記錄被執(zhí)行的寫命令(表示RDB異步生成快照期間的數(shù)據(jù)變更);
從服務(wù)器收到快照文件后丟棄所有舊數(shù)據(jù),載入收到的快照;
主服務(wù)器快照發(fā)送完畢后開始向從服務(wù)器發(fā)送緩沖區(qū)中的寫命令;
從服務(wù)器完成對快照的載入,開始接收命令請求,并執(zhí)行來自主服務(wù)器緩沖區(qū)的寫命令;

<center>圖5-2</center>
問題:生成RDB期間,master接收到的命令怎么處理?
開始生成RDB文件時,master會把所有新的寫命令緩存在內(nèi)存中。在 slave node 保存了RDB之后,再將新的寫命令復(fù)制給 slave node。(跟AOF重寫期間的思路是一樣的)
完成上面幾個步驟后就完成了slave服務(wù)器數(shù)據(jù)初始化的所有操作,savle服務(wù)器此時可以接收來自用戶的讀請求,同時,主從節(jié)點(diǎn)進(jìn)入到命令傳播階段,在這個階段主節(jié)點(diǎn)將自己執(zhí)行的寫命令發(fā)送給從節(jié)點(diǎn),從節(jié)點(diǎn)接收命令并執(zhí)行,從而保證主從節(jié)點(diǎn)數(shù)據(jù)的一致性。
在命令傳播階段,除了發(fā)送寫命令,主從節(jié)點(diǎn)還維持著心跳機(jī)制:PING和REPLCONF ACK,下面演示一下具體的實(shí)現(xiàn)。
在slave服務(wù)器redis cli上執(zhí)行 REPLCONF listening-port 6379 (向主數(shù)據(jù)庫發(fā)送replconf命令說明自己的端口號)
開始同步,向master服務(wù)器發(fā)送sync命令開始同步,此時master會發(fā)送快照文件和緩存的命令。
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會將收到的內(nèi)容寫入到硬盤上的臨時文件,當(dāng)寫入完成后會用該臨時文件替換原有的RDB快照文件。需要注意的是,在同步的過程中slave并不會阻塞,仍然可以處理客戶端的命令。默認(rèn)情況下slave會用同步前的數(shù)據(jù)對命令進(jìn)行響應(yīng),如果我們希望讀取的數(shù)據(jù)不能出現(xiàn)臟數(shù)據(jù),那么可以在redis.conf文件中配置下面的參數(shù),來使得slave在同步完成對所有命令之前,都回復(fù)錯誤:SYNC with master in progress
slave-serve-stale-data no -
復(fù)制階段結(jié)束后,master執(zhí)行的任何非查詢語句都會異步發(fā)送給slave。 可以在master節(jié)點(diǎn)執(zhí)行set命令,可以在slave節(jié)點(diǎn)看到如下同步的指令。
redis > sync "set","11","11" "ping"
另外需要注意的是:
master/slave 復(fù)制策略是采用樂觀復(fù)制,也就是說可以容忍在一定時間內(nèi)master/slave數(shù)據(jù)的內(nèi)容是不同的,但是兩者的數(shù)據(jù)會最終同步成功。
具體來說,redis的主從同步過程本身是異步的,意味著master執(zhí)行完客戶端請求的命令后會立即返回結(jié)果給客戶端,然后異步的方式把命令同步給slave。這一特征保證啟用master/slave后 master的性能不會受到影響。
但是另一方面,如果在這個數(shù)據(jù)不一致的窗口期間,master/slave因?yàn)榫W(wǎng)絡(luò)問題斷開連接,而這個時候,master是無法得知某個命令最終同步給了多少個slave數(shù)據(jù)庫。不過redis提供了一個配置項(xiàng)來限制只有數(shù)據(jù)至少同步給多少個slave的時候,master才是可寫的:
min-replicas-to-write 3 表示只有當(dāng)3個或以上的slave連接到master,master才是可寫的
min-replicas-max-lag 10 表示允許slave最長失去連接的時間,如果10秒還沒收到slave的響應(yīng),則master認(rèn)為該slave以斷開
修改master redis服務(wù)的redis.conf, 打開這兩個配置,重啟即可看到效果
增量復(fù)制
從Redis2.8開始,主從節(jié)點(diǎn)支持增量復(fù)制,并且是支持?jǐn)帱c(diǎn)續(xù)傳的增量復(fù)制,也就是說如果出現(xiàn)復(fù)制異?;蛘呔W(wǎng)絡(luò)連接斷開導(dǎo)致復(fù)制中斷的情況,在系統(tǒng)恢復(fù)之后仍然可以按照上次復(fù)制的地方繼續(xù)同步,而不是全量復(fù)制。
它的具體原理是:主節(jié)點(diǎn)和從節(jié)點(diǎn)分別維護(hù)一個復(fù)制偏移量(offset),代表的是主節(jié)點(diǎn)向從節(jié)點(diǎn)傳遞的字節(jié)數(shù);主節(jié)點(diǎn)每次向從節(jié)點(diǎn)傳播N個字節(jié)數(shù)據(jù)時,主節(jié)點(diǎn)的offset增加N;從節(jié)點(diǎn)每次收到主節(jié)點(diǎn)傳來的N個字節(jié)數(shù)據(jù)時,從節(jié)點(diǎn)的offset增加N。主從節(jié)點(diǎ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 # 開啟復(fù)制緩沖區(qū)
repl_backlog_size:1048576 # 緩沖區(qū)最大長度
repl_backlog_first_byte_offset:771 # 起始偏移量,計(jì)算當(dāng)前緩存區(qū)可用范圍
repl_backlog_histlen:77094 # 以保存數(shù)據(jù)的有效長度
無磁盤復(fù)制
前面我們說過,Redis復(fù)制的工作原理基于RDB方式的持久化實(shí)現(xiàn)的,也就是master在后臺保存RDB快照,slave接收到rdb文件并載入,但是這種方式會存在一些問題。
當(dāng)master禁用RDB時,如果執(zhí)行了復(fù)制初始化操作,Redis依然會生成RDB快照,當(dāng)master下次啟動時執(zhí)行該RDB文件的恢復(fù),但是因?yàn)閺?fù)制發(fā)生的時間點(diǎn)不確定,所以恢復(fù)的數(shù)據(jù)可能是任何時間點(diǎn)的。就會造成數(shù)據(jù)出現(xiàn)問題
當(dāng)硬盤性能比較慢的情況下(網(wǎng)絡(luò)硬盤),那初始化復(fù)制過程會對性能產(chǎn)生影響
因此2.8.18以后的版本,Redis引入了無硬盤復(fù)制選項(xiàng),可以不需要通過RDB文件去同步,直接發(fā)送數(shù)據(jù),通過以下配置來開啟該功能:
repl-diskless-sync yes
master在內(nèi)存中直接創(chuàng)建rdb,然后發(fā)送給slave,不會在自己本地落地磁盤了
主從復(fù)制注意事項(xiàng)
主從模式解決了數(shù)據(jù)備份和性能(通過讀寫分離)的問題,但是還是存在一些不足:
1、第一次建立復(fù)制的時候一定是全量復(fù)制,所以如果主節(jié)點(diǎn)數(shù)據(jù)量較大,那么復(fù)制延遲就比較長,此時應(yīng)該盡量避開流量的高峰期,避免造成阻塞;如果有多個從節(jié)點(diǎn)需要建立對主節(jié)點(diǎn)的復(fù)制,可以考慮將幾個從節(jié)點(diǎn)錯開,避免主節(jié)點(diǎn)帶寬占用過大。此外,如果從節(jié)點(diǎn)過多,也可以調(diào)整主從復(fù)制的拓?fù)浣Y(jié)構(gòu),由一主多從結(jié)構(gòu)變?yōu)闃錉罱Y(jié)構(gòu)。
2、在一主一從或者一主多從的情況下,如果主服務(wù)器掛了,對外提供的服務(wù)就不可用了,單點(diǎn)問題沒有得到解決。如果每次都是手動把之前的從服務(wù)器切換成主服務(wù)器,這個比較費(fèi)時費(fèi)力,還會造成一定時間的服務(wù)不可用。
Master自動選舉之Sentinel哨兵機(jī)制
在前面講的master/slave模式,在一個典型的一主多從的系統(tǒng)中,slave在整個體系中起到了數(shù)據(jù)冗余備份和讀寫分離的作用。當(dāng)master遇到異常終端后,開發(fā)者可以通過手動方式選擇一個slave數(shù)據(jù)庫來升級到master,使得系統(tǒng)能夠繼續(xù)提供服務(wù)。然后這個過程需要人工干預(yù),比較麻煩; redis并沒有提供自動master選舉功能,而是需要借助一個哨兵來進(jìn)行監(jiān)控。
什么是哨兵
顧名思義,哨兵的作用就是監(jiān)控Redis系統(tǒng)的運(yùn)行狀況,它的功能包括兩個
監(jiān)控master和slave是否正常運(yùn)行
master出現(xiàn)故障時自動將slave數(shù)據(jù)庫升級為master
哨兵是一個獨(dú)立的進(jìn)程,使用哨兵后的架構(gòu)如圖5-3所示,同時為了保證哨兵的高可用,我們會對Sentinel做集群部署,因此Sentinel不僅僅監(jiān)控Redis所有的主從節(jié)點(diǎn),Sentinel也會實(shí)現(xiàn)相互監(jiān)控。

<center>圖5-3</center>
配置哨兵集群
在前面主從復(fù)制的基礎(chǔ)上,增加三個sentinel節(jié)點(diǎn),來實(shí)現(xiàn)對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表示要監(jiān)控的master的名字,這個名字是自己定義,ip和port表示master的ip和端口號,最后一個2表示最低通過票數(shù),也就是說至少需要幾個哨兵節(jié)點(diǎn)認(rèn)為master下線才算是真的下線
sentinel monitor mymaster 192.168.221.128 6379 2
sentinel down-after-milliseconds mymaster 5000 # 表示如果5s內(nèi)mymaster沒響應(yīng),就認(rèn)為SDOWN
sentinel failover-timeout mymaster 15000 # 表示如果15秒后,mysater仍沒活過來,則啟動failover,從剩下的slave中選一個升級為master
logfile "/data/program/redis/logs/sentinels.log" # 需要提前創(chuàng)建好文件
- 通過下面這個命令啟動sentinel哨兵
./redis-sentinel ../sentinel.conf
- 啟動成功后,得到一下信息,表示哨兵啟動成功并且開始監(jiān)控集群節(jié)點(diǎn)
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
其他兩個節(jié)點(diǎn)的配置和上面完全相同,都去監(jiān)視master節(jié)點(diǎn)即可,主要,sentinel.conf文件中master節(jié)點(diǎn)的ip一定不能輸127.0.0.1,否則其他sentinel節(jié)點(diǎn)無法和它通信
當(dāng)其他sentinel哨兵節(jié)點(diǎn)啟動后,第一臺啟動的sentinel節(jié)點(diǎn)還會輸出如下日志,表示有其他sentinel節(jié)點(diǎn)加入進(jìn)來。
+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節(jié)點(diǎn)故障
我們直接把redis主從復(fù)制集群的master節(jié)點(diǎn),通過./redis-cli shutdown命令停止,于是我們觀察三個sentinel哨兵的日志,先來看第一臺啟動的sentinel日志,得到如下內(nèi)容。
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表示哨兵主觀認(rèn)為master已經(jīng)停止服務(wù)了。
+odown表示哨兵客觀認(rèn)為master停止服務(wù)了(關(guān)于主觀和客觀,后面會給大家講解)。
接著哨兵開始進(jìn)行故障恢復(fù),挑選一個slave升級為master,其他哨兵節(jié)點(diǎn)的日志。
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表示哨兵開始進(jìn)行故障恢復(fù)
+failover-end 表示哨兵完成故障恢復(fù)
+slave表示列出新的master和slave服務(wù)器,我們?nèi)匀豢梢钥吹揭呀?jīng)停掉的master,哨兵并沒有清除已停止的服務(wù)的實(shí)例,這是因?yàn)橐呀?jīng)停止的服務(wù)器有可能會在某個時間進(jìn)行恢復(fù),恢復(fù)以后會以slave角色加入到整個集群中。
實(shí)現(xiàn)原理
1):每個Sentinel以每秒鐘一次的頻率向它所知的Master/Slave以及其他 Sentinel 實(shí)例發(fā)送一個 PING 命令
2):如果一個實(shí)例(instance)距離最后一次有效回復(fù) PING 命令的時間超過 down-after-milliseconds 選項(xiàng)所指定的值, 則這個實(shí)例會被 Sentinel 標(biāo)記為主觀下線。
3):如果一個Master被標(biāo)記為主觀下線,則正在監(jiān)視這個Master的所有 Sentinel 要以每秒一次的頻率確認(rèn)Master的確進(jìn)入了主觀下線狀態(tài)。
4):當(dāng)有足夠數(shù)量的 Sentinel(大于等于配置文件指定的值:quorum)在指定的時間范圍內(nèi)確認(rèn)Master的確進(jìn)入了主觀下線狀態(tài), 則Master會被標(biāo)記為客觀下線 。
5):在一般情況下, 每個 Sentinel 會以每 10 秒一次的頻率向它已知的所有Master,Slave發(fā)送 INFO 命令
6):當(dāng)Master被 Sentinel 標(biāo)記為客觀下線時,Sentinel 向下線的 Master 的所有 Slave 發(fā)送 INFO 命令的頻率會從 10 秒一次改為每秒一次 ,若沒有足夠數(shù)量的 Sentinel 同意 Master 已經(jīng)下線, Master 的客觀下線狀態(tài)就會被移除。
8):若 Master 重新向 Sentinel 的 PING 命令返回有效回復(fù), Master 的主觀下線狀態(tài)就會被移除。
主觀下線:Subjectively Down,簡稱 SDOWN,指的是當(dāng)前 Sentinel 實(shí)例對某個redis服務(wù)器做出的下線判斷。
客觀下線:Objectively Down, 簡稱 ODOWN,指的是多個 Sentinel 實(shí)例在對Master Server做出 SDOWN 判斷,并且通過 SENTINEL之間交流后得出Master下線的判斷。然后開啟failover
誰來完成故障轉(zhuǎn)移?
當(dāng)redis中的master節(jié)點(diǎn)被判定為客觀下線之后,需要重新從slave節(jié)點(diǎn)選擇一個作為新的master節(jié)點(diǎn),那現(xiàn)在有三個sentinel節(jié)點(diǎn),應(yīng)該由誰來完成這個故障轉(zhuǎn)移過程呢?所以這三個sentinel節(jié)點(diǎn)必須要通過某種機(jī)制達(dá)成一致,在Redis中采用了Raft算法來實(shí)現(xiàn)這個功能。
每次master出現(xiàn)故障時,都會觸發(fā)raft算法來選擇一個leader完成redis主從集群中的master選舉功能。
數(shù)據(jù)一致性問題
了解raft算法之前,我們來了解一個拜占庭將軍問題。
拜占庭將軍問題(Byzantine failures),是由萊斯利·蘭伯特提出的點(diǎn)對點(diǎn)通信中的基本問題。具體含義是在存在消息丟失的不可靠信道上試圖通過消息傳遞的方式達(dá)到一致性是不可能的。
拜占庭位于如今的土耳其的伊斯坦布爾,是東羅馬帝國的首都。由于當(dāng)時拜占庭羅馬帝國國土遼闊,為了達(dá)到防御目的,每個軍隊(duì)都分隔很遠(yuǎn),將軍與將軍之間只能靠信差傳消息。在戰(zhàn)爭的時候,拜占庭軍隊(duì)內(nèi)所有將軍和副官必須達(dá)成一致的共識,決定是否有贏的機(jī)會才去攻打敵人的陣營。但是,在軍隊(duì)內(nèi)有可能存有叛徒和敵軍的間諜,左右將軍們的決定又?jǐn)_亂整體軍隊(duì)的秩序。在進(jìn)行共識時,結(jié)果并不代表大多數(shù)人的意見。這時候,在已知有成員謀反的情況下,其余忠誠的將軍在不受叛徒的影響下如何達(dá)成一致的協(xié)議,這就是是著名的拜占庭問題。
拜占庭將軍問題本質(zhì)上所描述的是計(jì)算機(jī)領(lǐng)域中的一個協(xié)議問題,拜占庭帝國軍隊(duì)的將軍們必須全體一致的決定是否攻擊某一支敵軍。問題是這些將軍在地理上是分隔開來的,并且將軍中存在叛徒。叛徒可以任意行動以達(dá)到以下目標(biāo):
- 欺騙某些將軍采取進(jìn)攻行動;
- 促成一個不是所有將軍都同意的決定,如當(dāng)將軍們不希望進(jìn)攻時促成進(jìn)攻行動;
- 或者迷惑某些將軍,使他們無法做出決定。
如果叛徒達(dá)到了這些目的之一,則任何攻擊行動的結(jié)果都是注定要失敗的,只有完全達(dá)成一致的努力才能獲得勝利 。
拜占庭假設(shè)是對現(xiàn)實(shí)世界的模型化,由于硬件錯誤、網(wǎng)絡(luò)擁塞或斷開以及遭到惡意攻擊,計(jì)算機(jī)和網(wǎng)絡(luò)可能出現(xiàn)不可預(yù)料的行為,所以如何在這樣的環(huán)境下達(dá)成一致,這就是所謂的數(shù)據(jù)一致性問題。
回到Sentinel中,這三個Sentinel節(jié)點(diǎn),需要選擇一個節(jié)點(diǎn)來負(fù)責(zé)針對redis集群進(jìn)行故障恢復(fù),那這三個節(jié)點(diǎn)中誰能做這個事情?因此同樣需要基于某個機(jī)制來達(dá)成共識。
在很多中間件中都需要用到數(shù)據(jù)一致性算法,最直觀的是像zookeeper這樣一個組件,他的高可用設(shè)計(jì)是由leader和follow組成,當(dāng)leader節(jié)點(diǎn)因?yàn)楫惓e礄C(jī)了, 需要從生下的follow節(jié)點(diǎn)選舉出一個新的leader節(jié)點(diǎn),那么這個選舉過程需要集群中所有節(jié)點(diǎn)達(dá)成一致,也就是只有所有節(jié)點(diǎn)都贊同某個follow節(jié)點(diǎn)成為leader,它才能成為leader節(jié)點(diǎn)。而這個共識達(dá)成的前提是所有節(jié)點(diǎn)需要對某個投票結(jié)果達(dá)成一致,否則就無法選舉出新的leader,因此這里必然需要用到共識算法。
常見的數(shù)據(jù)一致性算法
- paxos,paxos應(yīng)該是最早也是最正統(tǒng)的數(shù)據(jù)一致性算法,也是最復(fù)雜難懂的算法。
- raft,raft算法應(yīng)該是最通俗易懂的一致性算法,它在nacos、sentinel、consul等組件中都有使用。
- zab協(xié)議,是zookeeper中基于paxos算法上演變過來的一種一致性算法
- distro,Distro協(xié)議。Distro是阿里巴巴的私有協(xié)議,目前流行的Nacos服務(wù)管理框架就采用了Distro協(xié)議。Distro 協(xié)議被定位為 臨時數(shù)據(jù)的一致性協(xié)議
Raft協(xié)議說明
Raft算法動畫演示地址: http://thesecretlivesofdata.com/raft/
Raft算法的核心思想:先到先得,少數(shù)服從多數(shù)。
故障轉(zhuǎn)移過程
怎么讓一個原來的slave節(jié)點(diǎn)成為主節(jié)點(diǎn)?
選出Sentinel Leader之后,由Sentinel Leader向某個節(jié)點(diǎn)發(fā)送slaveof no one命令,讓它成為獨(dú)立節(jié)點(diǎn)。
然后向其他節(jié)點(diǎn)發(fā)送replicaof x.x.x.x xxxx(本機(jī)服務(wù)),讓它們成為這個節(jié)點(diǎn)的子節(jié)點(diǎn),故障轉(zhuǎn)移完成。
如何選擇合適的slave節(jié)點(diǎn)成為master呢?有四個因素影響。
- 斷開連接時長,如果與哨兵連接斷開的比較久,超過了某個閾值,就直接失去了選舉權(quán)
- 優(yōu)先級排序,如果擁有選舉權(quán),那就看誰的優(yōu)先級高,這個在配置文件里可以設(shè)置(replica-priority 100),數(shù)值越小優(yōu)先級越高
- 復(fù)制數(shù)量,如果優(yōu)先級相同,就看誰從master中復(fù)制的數(shù)據(jù)最多(復(fù)制偏移量最大)
- 進(jìn)程id,如果復(fù)制數(shù)量也相同,就選擇進(jìn)程id最小的那個
Sentinel功能總結(jié)
監(jiān)控:Sentinel會不斷檢查主服務(wù)器和從服務(wù)器是否正常運(yùn)行。
通知:如果某一個被監(jiān)控的實(shí)例出現(xiàn)問題,Sentinel可以通過API發(fā)出通知。
自動故障轉(zhuǎn)移(failover):如果主服務(wù)器發(fā)生故障,Sentinel可以啟動故障轉(zhuǎn)移過程。把某臺服務(wù)器升級為主服務(wù)器,并發(fā)出通知。
配置管理:客戶端連接到Sentinel,獲取當(dāng)前的Redis主服務(wù)器的地址。
Redis分布式擴(kuò)展之Redis Cluster方案
主從切換的過程中會丟失數(shù)據(jù),因?yàn)橹挥幸粋€master,只能單點(diǎn)寫,沒有解決水平擴(kuò)容的問題。而且每個節(jié)點(diǎn)都保存了所有數(shù)據(jù),一個是內(nèi)存的占用率較高,另外就是如果進(jìn)行數(shù)據(jù)恢復(fù)時,非常慢。而且數(shù)據(jù)量過大對數(shù)據(jù)IO操作的性能也會有影響。
所以我們同樣也有對Redis數(shù)據(jù)分片的需求,所謂分片就是把一份大數(shù)據(jù)拆分成多份小數(shù)據(jù),在3.0之前,我們只能通過構(gòu)建多個redis主從節(jié)點(diǎn)集群,把不同業(yè)務(wù)數(shù)據(jù)拆分到不冉的集群中,這種方式在業(yè)務(wù)層需要有大量的代碼來完成數(shù)據(jù)分片、路由等工作,導(dǎo)致維護(hù)成本高、增加、移除節(jié)點(diǎn)比較繁瑣。
Redis3.0之后引入了Redis Cluster集群方案,它用來解決分布式擴(kuò)展的需求,同時也實(shí)現(xiàn)了高可用機(jī)制。
Redis Cluster架構(gòu)
一個Redis Cluster由多個Redis節(jié)點(diǎn)構(gòu)成,不同節(jié)點(diǎn)組服務(wù)的數(shù)據(jù)沒有交集,也就是每個一節(jié)點(diǎn)組對應(yīng)數(shù)據(jù)sharding的一個分片。
節(jié)點(diǎn)組內(nèi)部分為主備兩類節(jié)點(diǎn),對應(yīng)master和slave節(jié)點(diǎn)。兩者數(shù)據(jù)準(zhǔn)實(shí)時一致,通過異步化訂的主備復(fù)制機(jī)制來保證。
一個節(jié)點(diǎn)組有且只有一個master節(jié)點(diǎn),同時可以有0到多個slave節(jié)點(diǎn),在這個節(jié)點(diǎn)組中只有master節(jié)點(diǎn)對用戶提供些服務(wù),讀服務(wù)可以由master或者slave提供。如圖5-4中,包含三個master節(jié)點(diǎn)以及三個master對應(yīng)的slave節(jié)點(diǎn),一般一組集群至少要6個節(jié)點(diǎn)才能保證完整的高可用。
其中三個master會分配不同的slot(表示數(shù)據(jù)分片區(qū)間),當(dāng)master出現(xiàn)故障時,slave會自動選舉成為master頂替主節(jié)點(diǎn)繼續(xù)提供服務(wù)。

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

<center>圖5-5</center>
gossip協(xié)議消息
gossip協(xié)議包含多種消息,包括ping,pong,meet,fail等等。
ping:每個節(jié)點(diǎn)都會頻繁給其他節(jié)點(diǎn)發(fā)送ping,其中包含自己的狀態(tài)還有自己維護(hù)的集群元數(shù)據(jù),互相通過ping交換元數(shù)據(jù);
pong: 返回ping和meet,包含自己的狀態(tài)和其他信息,也可以用于信息廣播和更新;
fail: 某個節(jié)點(diǎn)判斷另一個節(jié)點(diǎn)fail之后,就發(fā)送fail給其他節(jié)點(diǎn),通知其他節(jié)點(diǎn),指定的節(jié)點(diǎn)宕機(jī)了。
meet:某個節(jié)點(diǎn)發(fā)送meet給新加入的節(jié)點(diǎn),讓新節(jié)點(diǎn)加入集群中,然后新節(jié)點(diǎn)就會開始與其他節(jié)點(diǎn)進(jìn)行通信,不需要發(fā)送形成網(wǎng)絡(luò)的所需的所有CLUSTER MEET命令。發(fā)送CLUSTER MEET消息以便每個節(jié)點(diǎn)能夠達(dá)到其他每個節(jié)點(diǎn)只需通過一條已知的節(jié)點(diǎn)鏈就夠了。由于在心跳包中會交換gossip信息,將會創(chuàng)建節(jié)點(diǎn)間缺失的鏈接。
gossip的優(yōu)缺點(diǎn)
優(yōu)點(diǎn): gossip協(xié)議的優(yōu)點(diǎn)在于元數(shù)據(jù)的更新比較分散,不是集中在一個地方,更新請求會陸陸續(xù)續(xù),打到所有節(jié)點(diǎn)上去更新有一定的延時,降低了壓力; 去中心化、可擴(kuò)展、容錯、一致性收斂、簡單。 由于不能保證某個時刻所有節(jié)點(diǎn)都收到消息,但是理論上最終所有節(jié)點(diǎn)都會收到消息,因此它是一個最終一致性協(xié)議。
缺點(diǎn): 元數(shù)據(jù)更新有延時可能導(dǎo)致集群的一些操作會有一些滯后。 消息的延遲 , 消息冗余 。
Redis Cluster集群搭建
集群至少需要6個節(jié)點(diǎn)(3主3從模式),每一個節(jié)點(diǎn)可以搭建在同一臺機(jī)器上,也可以搭建在不同的服務(wù)器上。
192.168.221.128 7000 、 7001
192.168.221.129 7002 、 7003
192.168.221.130 7004 、 7005
分別啟動6個節(jié)點(diǎn)。
- 在redis安裝目錄下,分別創(chuàng)建以下目錄,這些目錄必須要提前創(chuàng)建好,redis啟動時不會主動創(chuàng)建這些目錄。
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目錄下,由于只有三臺機(jī)器,所以每個機(jī)器上需要運(yùn)行兩個redis-server,因此需要修改redis.conf文件的名字來做區(qū)分,redis_7000.conf。并且修改該文件的一下內(nèi)容。
pidfile "/data/program/redis/run/redis_7000.pid" #pid存儲目錄
logfile "/data/program/redis/logs/redis_7000.log" #日志存儲目錄
dir "/data/program/redis/data/7000" #數(shù)據(jù)存儲目錄,目錄要提前創(chuàng)建好
cluster-enabled yes #開啟集群
cluster-config-file nodes-7000.conf #集群節(jié)點(diǎn)配置文件,這個文件是不能手動編輯的。確保每一個集群節(jié)點(diǎn)的配置文件不同
cluster-node-timeout 15000 #集群節(jié)點(diǎn)的超時時間,單位:ms,超時后集群會認(rèn)為該節(jié)點(diǎn)失敗
- 每個節(jié)點(diǎn)需要啟動兩個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" #數(shù)據(jù)存儲目錄,目錄要提前創(chuàng)建好
cluster-enabled yes #開啟集群
cluster-config-file nodes-7001.conf #集群節(jié)點(diǎn)配置文件,這個文件是不能手動編輯的。確保每一個集群節(jié)點(diǎn)的配置文件不同
cluster-node-timeout 15000 #集群節(jié)點(diǎn)的超時時間,單位:ms,超時后集群會認(rèn)為該節(jié)點(diǎn)失敗
-
創(chuàng)建兩個腳本用來進(jìn)行統(tǒng)一的服務(wù)運(yùn)行
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
通過下面命令讓上述腳本擁有執(zhí)行權(quán)限
chmod +x cluster-*.sh
- 其他兩個節(jié)點(diǎn)重復(fù)上述的過程,完成6個節(jié)點(diǎn)的啟動。
配置redis 集群
啟動完這5臺服務(wù)器后,需要通過下面的操作來配置集群節(jié)點(diǎn)。在redis6.0版本中,創(chuàng)建集群的方式為redis-cli方式直接創(chuàng)建,以下命令在任意一臺服務(wù)器上執(zhí)行即可
用以下命令創(chuàng)建集群,--cluster-replicas 1 參數(shù)表示希望每個主服務(wù)器都有一個從服務(wù)器,這里則代表3主3從,通過該方式創(chuàng)建的帶有從節(jié)點(diǎn)的機(jī)器不能夠自己手動指定主節(jié)點(diǎn),redis集群會盡量把主從服務(wù)器分配在不同機(jī)器上
[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
執(zhí)行上述命令后,會得到以下執(zhí)行結(jié)果,
>>> 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
從上述結(jié)果中看到兩個點(diǎn):
- 預(yù)先分配三個節(jié)點(diǎn)的slot區(qū)間
- 自動選擇合適的節(jié)點(diǎn)作為master
查看集群狀態(tài)等信息
- cluster info 查看集群狀態(tài)信息
[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
- 查看集群節(jié)點(diǎn)信息
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
數(shù)據(jù)分布
Redis Cluster中,Sharding采用slot(槽)的概念,一共分成16384個槽,這有點(diǎn)兒類似pre sharding思路。對于每個進(jìn)入Redis的鍵值對,根據(jù)key進(jìn)行散列,分配到這16384個slot中的某一個中。使用的hash算法也比較簡單,就是CRC16后16384取模[crc16(key)%16384]。
Redis集群中的每個node(節(jié)點(diǎn))負(fù)責(zé)分?jǐn)傔@16384個slot中的一部分,也就是說,每個slot都對應(yīng)一個node負(fù)責(zé)處理。
如圖5-6所示,假設(shè)現(xiàn)在我們是三個主節(jié)點(diǎn)分別是:A, B, C 三個節(jié)點(diǎn),它們可以是一臺機(jī)器上的三個端口,也可以是三臺不同的服務(wù)器。那么,采用哈希槽 (hash slot)的方式來分配16384個slot 的話,它們?nèi)齻€節(jié)點(diǎn)分別承擔(dān)的slot 區(qū)間是:
節(jié)點(diǎn)A覆蓋0-5000;
節(jié)點(diǎn)B覆蓋5001-10000;
節(jié)點(diǎn)C覆蓋10001-16383

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

<center>圖5-7</center>