到 目前 爲止,咱們所學習的 Redis 都是 單機版 的,這也就意味着一旦咱們所依賴的 Redis 服務宕機了,咱們的主流程也會受到必定的影響,這固然是咱們不可以接受的。html
因此一開始咱們的想法是:搞一臺備用機。這樣咱們就能夠在一臺服務器出現問題的時候切換動態地到另外一臺去:node
幸運的是,兩個節點數據的同步咱們可使用 Redis 的 主從同步 功能幫助到咱們,這樣一來,有個備份,內心就踏實多了。git
後來由於某種神祕力量,Redis 老會在莫名其妙的時間點出問題 (好比半夜 2 點),我總不能 24 小時時刻守在電腦旁邊切換節點吧,因而另外一個想法又開始了:給全部的節點找一個 "管家",自動幫我監聽照顧節點的狀態並切換:程序員
這大概就是 Redis 哨兵 (Sentinel) 的簡單理解啦。什麼?管家宕機了怎麼辦?相較於有大量請求的 Redis 服務來講,管家宕機的機率就要小得多啦.. 若是真的宕機了,咱們也能夠直接切換成當前可用的節點保證可用..github
好了,經過上面的一些解決方案咱們對 Redis 的 穩定性 稍微有了一些底氣了,但單臺節點的計算能力始終有限,所謂人多力量大,若是咱們把 多個節點組合 成 一個可用的工做節點,那就大大增長了 Redis 的 高可用、可擴展、分佈式、容錯 等特性:web
主從複製,是指將一臺 Redis 服務器的數據,複製到其餘的 Redis 服務器。前者稱爲 主節點(master),後者稱爲 從節點(slave)。且數據的複製是 單向 的,只能由主節點到從節點。Redis 主從複製支持 主從同步 和 從從同步 兩種,後者是 Redis 後續版本新增的功能,以減輕主節點的同步負擔。redis
在 Redis 中,用戶能夠經過執行 SLAVEOF
命令或者設置 slaveof
選項,讓一個服務器去複製另外一個服務器,如下三種方式是 徹底等效 的:算法
slaveof <masterip> <masterport>
--slaveof <masterip> <masterport>
slaveof <masterip> <masterport>
,讓該 Redis 實例成爲從節點。
須要注意的是:主從複製的開啓,徹底是在從節點發起的,不須要咱們在主節點作任何事情。數組
在正確安裝好 Redis 以後,咱們可使用 redis-server --port <port>
的方式指定建立兩個不一樣端口的 Redis 實例,例如,下方我分別建立了一個 6379
和 6380
的兩個 Redis 實例:安全
# 建立一個端口爲 6379 的 Redis 實例
redis-server --port 6379
# 建立一個端口爲 6380 的 Redis 實例
redis-server --port 6380
複製代碼
此時兩個 Redis 節點啓動後,都默認爲 主節點。
咱們在 6380
端口的節點中執行 slaveof
命令,使之變爲從節點:
# 在 6380 端口的 Redis 實例中使用控制檯
redis-cli -p 6380
# 成爲本地 6379 端口實例的從節點
127.0.0.1:6380> SLAVEOF 127.0.0.1ø 6379
OK
複製代碼
下面咱們來驗證一下,主節點的數據是否會複製到從節點之中:
127.0.0.1:6380> GET mykey
(nil)
複製代碼
127.0.0.1:6379> SET mykey myvalue
OK
複製代碼
127.0.0.1:6380> GET mykey
"myvalue"
複製代碼
經過 slaveof <masterip> <masterport>
命令創建主從複製關係之後,能夠經過 slaveof no one
斷開。須要注意的是,從節點斷開復制後,不會刪除已有的數據,只是再也不接受主節點新的數據變化。
從節點執行 slaveof no one
以後,從節點和主節點分別打印日誌以下:、
# 從節點打印日誌
61496:M 17 Mar 2020 08:10:22.749 # Connection with master lost.
61496:M 17 Mar 2020 08:10:22.749 * Caching the disconnected master state.
61496:M 17 Mar 2020 08:10:22.749 * Discarding previously cached master state.
61496:M 17 Mar 2020 08:10:22.749 * MASTER MODE enabled (user request from 'id=4 addr=127.0.0.1:55096 fd=8 name= age=1664 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=34 qbuf-free=32734 obl=0 oll=0 omem=0 events=r cmd=slaveof')
# 主節點打印日誌
61467:M 17 Mar 2020 08:10:22.749 # Connection with replica 127.0.0.1:6380 lost.
複製代碼
爲了節省篇幅,我把主要的步驟都 濃縮 在了上圖中,其實也能夠 簡化成三個階段:準備階段-數據同步階段-命令傳播階段。下面咱們來進行一些必要的說明。
在上面的 快速體驗 過程當中,你會發現 slaveof
這個命令竟然不須要驗證?這意味着只要知道了 ip 和端口就能夠隨意拷貝服務器上的數據了?
那固然不可以了,咱們能夠經過在 主節點 配置 requirepass
來設置密碼,這樣就必須在 從節點 中對應配置好 masterauth
參數 (與主節點 requirepass
保持一致) 纔可以進行正常複製了。
每次執行 SYNC
命令,主從服務器須要執行以下動做:
BGSAVE
命令來生成 RDB 文件,這個生成操做會
消耗 主服務器大量的
CPU、內存和磁盤 I/O 的資源;
特別是當出現 斷線重複制 的狀況是時,爲了讓從服務器補足斷線時確實的那一小部分數據,卻要執行一次如此耗資源的 SYNC
命令,顯然是不合理的。
因此在 Redis 2.8 中引入了 PSYNC
命令來代替 SYNC
,它具備兩種模式:
部分複製的原理主要是靠主從節點分別維護一個 複製偏移量,有了這個偏移量以後斷線重連以後一比較,以後就能夠僅僅把從服務器斷線以後確實的這部分數據給補回來了。
更多的詳細內容能夠參考下方 參考資料 3
上圖 展現了一個典型的哨兵架構圖,它由兩部分組成,哨兵節點和數據節點:
在複製的基礎上,哨兵實現了 自動化的故障恢復 功能,下方是官方對於哨兵功能的描述:
其中,監控和自動故障轉移功能,使得哨兵能夠及時發現主節點故障並完成轉移。而配置提供者和通知功能,則須要在與客戶端的交互中才能體現。
正確安裝好 Redis 以後,咱們去到 Redis 的安裝目錄 (mac 默認在 /usr/local/
),找到 redis.conf
文件複製三份分別命名爲 redis-master.conf
/redis-slave1.conf
/redis-slave2.conf
,分別做爲 1
個主節點和 2
個從節點的配置文件 (下圖演示了我本機的 redis.conf
文件的位置)
打開能夠看到這個 .conf
後綴的文件裏面有不少說明的內容,所有刪除而後分別改爲下面的樣子:
#redis-master.conf
port 6379
daemonize yes
logfile "6379.log"
dbfilename "dump-6379.rdb"
#redis-slave1.conf
port 6380
daemonize yes
logfile "6380.log"
dbfilename "dump-6380.rdb"
slaveof 127.0.0.1 6379
#redis-slave2.conf
port 6381
daemonize yes
logfile "6381.log"
dbfilename "dump-6381.rdb"
slaveof 127.0.0.1 6379
複製代碼
而後咱們能夠執行 redis-server <config file path>
來根據配置文件啓動不一樣的 Redis 實例,依次啓動主從節點:
redis-server /usr/local/redis-5.0.3/redis-master.conf
redis-server /usr/local/redis-5.0.3/redis-slave1.conf
redis-server /usr/local/redis-5.0.3/redis-slave2.conf
複製代碼
節點啓動後,咱們執行 redis-cli
默認鏈接到咱們端口爲 6379
的主節點執行 info Replication
檢查一下主從狀態是否正常:(能夠看到下方正確地顯示了兩個從節點)
按照上面一樣的方法,咱們給哨兵節點也建立三個配置文件。(哨兵節點本質上是特殊的 Redis 節點,因此配置幾乎沒什麼差異,只是在端口上作區分就好)
# redis-sentinel-1.conf
port 26379
daemonize yes
logfile "26379.log"
sentinel monitor mymaster 127.0.0.1 6379 2
# redis-sentinel-2.conf
port 26380
daemonize yes
logfile "26380.log"
sentinel monitor mymaster 127.0.0.1 6379 2
# redis-sentinel-3.conf
port 26381
daemonize yes
logfile "26381.log"
sentinel monitor mymaster 127.0.0.1 6379 2
複製代碼
其中,sentinel monitor mymaster 127.0.0.1 6379 2
配置的含義是:該哨兵節點監控 127.0.0.1:6379
這個主節點,該主節點的名稱是 mymaster
,最後的 2
的含義與主節點的故障斷定有關:至少須要 2
個哨兵節點贊成,才能斷定主節點故障並進行故障轉移。
執行下方命令將哨兵節點啓動起來:
redis-server /usr/local/redis-5.0.3/redis-sentinel-1.conf --sentinel
redis-server /usr/local/redis-5.0.3/redis-sentinel-2.conf --sentinel
redis-server /usr/local/redis-5.0.3/redis-sentinel-3.conf --sentinel
複製代碼
使用 redis-cil
工具鏈接哨兵節點,並執行 info Sentinel
命令來查看是否已經在監視主節點了:
# 鏈接端口爲 26379 的 Redis 節點
➜ ~ redis-cli -p 26379
127.0.0.1:26379> info Sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3
複製代碼
此時你打開剛纔寫好的哨兵配置文件,你還會發現出現了一些變化:
首先,咱們使用 kill -9
命令來殺掉主節點,同時 在哨兵節點中執行 info Sentinel
命令來觀察故障節點的過程:
➜ ~ ps aux | grep 6379
longtao 74529 0.3 0.0 4346936 2132 ?? Ss 10:30上午 0:03.09 redis-server *:26379 [sentinel]
longtao 73541 0.2 0.0 4348072 2292 ?? Ss 10:18上午 0:04.79 redis-server *:6379
longtao 75521 0.0 0.0 4286728 728 s008 S+ 10:39上午 0:00.00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn 6379
longtao 74836 0.0 0.0 4289844 944 s006 S+ 10:32上午 0:00.01 redis-cli -p 26379
➜ ~ kill -9 73541
複製代碼
若是 剛殺掉瞬間 在哨兵節點中執行 info
命令來查看,會發現主節點尚未切換過來,由於哨兵發現主節點故障並轉移須要一段時間:
# 第一時間查看哨兵節點發現並未轉移,還在 6379 端口
127.0.0.1:26379> info Sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3
複製代碼
一段時間以後你再執行 info
命令,查看,你就會發現主節點已經切換成了 6381
端口的從節點:
# 過一段時間以後在執行,發現已經切換了 6381 端口
127.0.0.1:26379> info Sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6381,slaves=2,sentinels=3
複製代碼
但同時還能夠發現,哨兵節點認爲新的主節點仍然有兩個從節點 (上方 slaves=2),這是由於哨兵在將 6381
切換成主節點的同時,將 6379
節點置爲其從節點。雖然 6379
從節點已經掛掉,可是因爲 哨兵並不會對從節點進行客觀下線,所以認爲該從節點一直存在。當 6379
節點從新啓動後,會自動變成 6381
節點的從節點。
另外,在故障轉移的階段,哨兵和主從節點的配置文件都會被改寫:
slaveof
配置的變化,新的主節點沒有了
slaveof
配置,其從節點則
slaveof
新的主節點。
上面咱們在 快速體驗 中主要感覺到了服務端本身對於當前主從節點的自動化治理,下面咱們以 Java 代碼爲例,來演示一下客戶端如何訪問咱們的哨兵系統:
public static void testSentinel() throws Exception {
String masterName = "mymaster";
Set<String> sentinels = new HashSet<>();
sentinels.add("127.0.0.1:26379");
sentinels.add("127.0.0.1:26380");
sentinels.add("127.0.0.1:26381");
// 初始化過程作了不少工做
JedisSentinelPool pool = new JedisSentinelPool(masterName, sentinels);
Jedis jedis = pool.getResource();
jedis.set("key1", "value1");
pool.close();
}
複製代碼
Jedis 客戶端對哨兵提供了很好的支持。如上述代碼所示,咱們只須要向 Jedis 提供哨兵節點集合和 masterName
,構造 JedisSentinelPool
對象,而後即可以像使用普通 Redis 鏈接池同樣來使用了:經過 pool.getResource()
獲取鏈接,執行具體的命令。
在整個過程當中,咱們的代碼不須要顯式的指定主節點的地址,就能夠鏈接到主節點;代碼中對故障轉移沒有任何體現,就能夠在哨兵完成故障轉移後自動的切換主節點。之因此能夠作到這一點,是由於在 JedisSentinelPool
的構造器中,進行了相關的工做;主要包括如下兩點:
masterName
得到主節點的信息;該功能是經過調用哨兵節點的
sentinel get-master-addr-by-name
命令實現;
故障轉移操做的第一步 要作的就是在已下線主服務器屬下的全部從服務器中,挑選出一個狀態良好、數據完整的從服務器,而後向這個從服務器發送 slaveof no one
命令,將這個從服務器轉換爲主服務器。可是這個從服務器是怎麼樣被挑選出來的呢?
簡單來講 Sentinel 使用如下規則來選擇新的主服務器:
上圖 展現了 Redis Cluster 典型的架構圖,集羣中的每個 Redis 節點都 互相兩兩相連,客戶端任意 直連 到集羣中的 任意一臺,就能夠對其餘 Redis 節點進行 讀寫 的操做。
Redis 集羣中內置了 16384
個哈希槽。當客戶端鏈接到 Redis 集羣以後,會同時獲得一份關於這個 集羣的配置信息,當客戶端具體對某一個 key
值進行操做時,會計算出它的一個 Hash 值,而後把結果對 16384
求餘數,這樣每一個 key
都會對應一個編號在 0-16383
之間的哈希槽,Redis 會根據節點數量 大體均等 的將哈希槽映射到不一樣的節點。
再結合集羣的配置信息就可以知道這個 key
值應該存儲在哪個具體的 Redis 節點中,若是不屬於本身管,那麼就會使用一個特殊的 MOVED
命令來進行一個跳轉,告訴客戶端去鏈接這個節點以獲取數據:
GET x
-MOVED 3999 127.0.0.1:6381
複製代碼
MOVED
指令第一個參數 3999
是 key
對應的槽位編號,後面是目標節點地址,MOVED
命令前面有一個減號,表示這是一個錯誤的消息。客戶端在收到 MOVED
指令後,就當即糾正本地的 槽位映射表,那麼下一次再訪問 key
時就可以到正確的地方去獲取了。
bgsave
和
bgrewriteaof
的
fork
操做可能致使主進程阻塞,主從環境下主機切換時可能致使從節點長時間沒法提供服務,全量複製階段主節點的複製緩衝區可能溢出……
首先咱們找一個地方建立一個名爲 redis-cluster
的目錄:
mkdir -p ~/Desktop/redis-cluster
複製代碼
而後按照上面的方法,建立六個配置文件,分別命名爲:redis_7000.conf
/redis_7001.conf
.....redis_7005.conf
,而後根據不一樣的端口號修改對應的端口值就行了:
# 後臺執行
daemonize yes
# 端口號
port 7000
# 爲每個集羣節點指定一個 pid_file
pidfile ~/Desktop/redis-cluster/redis_7000.pid
# 啓動集羣模式
cluster-enabled yes
# 每個集羣節點都有一個配置文件,這個文件是不能手動編輯的。確保每個集羣節點的配置文件不通
cluster-config-file nodes-7000.conf
# 集羣節點的超時時間,單位:ms,超時後集羣會認爲該節點失敗
cluster-node-timeout 5000
# 最後將 appendonly 改爲 yes(AOF 持久化)
appendonly yes
複製代碼
記得把對應上述配置文件中根端口對應的配置都修改掉 (port/ pidfile/ cluster-config-file)。
redis-server ~/Desktop/redis-cluster/redis_7000.conf
redis-server ~/Desktop/redis-cluster/redis_7001.conf
redis-server ~/Desktop/redis-cluster/redis_7002.conf
redis-server ~/Desktop/redis-cluster/redis_7003.conf
redis-server ~/Desktop/redis-cluster/redis_7004.conf
redis-server ~/Desktop/redis-cluster/redis_7005.conf
複製代碼
而後執行 ps -ef | grep redis
查看是否啓動成功:
能夠看到 6
個 Redis 節點都以集羣的方式成功啓動了,可是如今每一個節點還處於獨立的狀態,也就是說它們每個都各自成了一個集羣,尚未互相聯繫起來,咱們須要手動地把他們之間創建起聯繫。
執行下列命令:
redis-cli --cluster create --cluster-replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005
複製代碼
--replicas 1
的意思是:咱們但願爲集羣中的每一個主節點建立一個從節點。
觀察控制檯輸出:
看到 [OK]
的信息以後,就表示集羣已經搭建成功了,能夠看到,這裏咱們正確地建立了三主三從的集羣。
咱們先使用 redic-cli
任意鏈接一個節點:
redis-cli -c -h 127.0.0.1 -p 7000
127.0.0.1:7000>
複製代碼
-c
表示集羣模式;
-h
指定 ip 地址;
-p
指定端口。
而後隨便 set
一些值觀察控制檯輸入:
127.0.0.1:7000> SET name wmyskxz
-> Redirected to slot [5798] located at 127.0.0.1:7001
OK
127.0.0.1:7001>
複製代碼
能夠看到這裏 Redis 自動幫咱們進行了 Redirected
操做跳轉到了 7001
這個實例上。
咱們再使用 cluster info
(查看集羣信息) 和 cluster nodes
(查看節點列表) 來分別看看:(任意節點輸入都可)
127.0.0.1:7001> 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:2
cluster_stats_messages_ping_sent:1365
cluster_stats_messages_pong_sent:1358
cluster_stats_messages_meet_sent:4
cluster_stats_messages_sent:2727
cluster_stats_messages_ping_received:1357
cluster_stats_messages_pong_received:1369
cluster_stats_messages_meet_received:1
cluster_stats_messages_received:2727
127.0.0.1:7001> CLUSTER NODES
56a04742f36c6e84968cae871cd438935081e86f 127.0.0.1:7003@17003 slave 4ec8c022e9d546c9b51deb9d85f6cf867bf73db6 0 1584428884000 4 connected
4ec8c022e9d546c9b51deb9d85f6cf867bf73db6 127.0.0.1:7000@17000 master - 0 1584428884000 1 connected 0-5460
e2539c4398b8258d3f9ffa714bd778da107cb2cd 127.0.0.1:7005@17005 slave a3406db9ae7144d17eb7df5bffe8b70bb5dd06b8 0 1584428885222 6 connected
d31cd1f423ab1e1849cac01ae927e4b6950f55d9 127.0.0.1:7004@17004 slave 236cefaa9cdc295bc60a5bd1aed6a7152d4f384d 0 1584428884209 5 connected
236cefaa9cdc295bc60a5bd1aed6a7152d4f384d 127.0.0.1:7001@17001 myself,master - 0 1584428882000 2 connected 5461-10922
a3406db9ae7144d17eb7df5bffe8b70bb5dd06b8 127.0.0.1:7002@17002 master - 0 1584428884000 3 connected 10923-16383
127.0.0.1:7001>
複製代碼
哈希取餘分區思路很是簡單:計算 key
的 hash 值,而後對節點數量進行取餘,從而決定數據映射到哪一個節點上。
不過該方案最大的問題是,當新增或刪減節點時,節點數量發生變化,系統中全部的數據都須要 從新計算映射關係,引起大規模數據遷移。
一致性哈希算法將 整個哈希值空間 組織成一個虛擬的圓環,範圍是 [0 - 232 - 1],對於每個數據,根據 key
計算 hash 值,確數據在環上的位置,而後今後位置沿順時針行走,找到的第一臺服務器就是其應該映射到的服務器:
與哈希取餘分區相比,一致性哈希分區將 增減節點的影響限制在相鄰節點。以上圖爲例,若是在 node1
和 node2
之間增長 node5
,則只有 node2
中的一部分數據會遷移到 node5
;若是去掉 node2
,則原 node2
中的數據只會遷移到 node4
中,只有 node4
會受影響。
一致性哈希分區的主要問題在於,當 節點數量較少 時,增長或刪減節點,對單個節點的影響可能很大,形成數據的嚴重不平衡。仍是以上圖爲例,若是去掉 node2
,node4
中的數據由總數據的 1/4
左右變爲 1/2
左右,與其餘節點相比負載太高。
該方案在 一致性哈希分區的基礎上,引入了 虛擬節點 的概念。Redis 集羣使用的即是該方案,其中的虛擬節點稱爲 槽(slot)。槽是介於數據和實際節點之間的虛擬概念,每一個實際節點包含必定數量的槽,每一個槽包含哈希值在必定範圍內的數據。
在使用了槽的一致性哈希分區中,槽是數據管理和遷移的基本單位。槽 解耦 了 數據和實際節點 之間的關係,增長或刪除節點對系統的影響很小。仍以上圖爲例,系統中有 4
個實際節點,假設爲其分配 16
個槽(0-15);
若是此時刪除 node2
,只須要將槽 4-7 從新分配便可,例如槽 4-5 分配給 node1
,槽 6 分配給 node3
,槽 7 分配給 node4
;能夠看出刪除 node2
後,數據在其餘節點的分佈仍然較爲均衡。
集羣的創建離不開節點之間的通訊,例如咱們上訪在 快速體驗 中剛啓動六個集羣節點以後經過 redis-cli
命令幫助咱們搭建起來了集羣,實際上背後每一個集羣之間的兩兩鏈接是經過了 CLUSTER MEET <ip> <port>
命令發送 MEET
消息完成的,下面咱們展開詳細說說。
在 哨兵系統 中,節點分爲 數據節點 和 哨兵節點:前者存儲數據,後者實現額外的控制功能。在 集羣 中,沒有數據節點與非數據節點之分:全部的節點都存儲數據,也都參與集羣狀態的維護。爲此,集羣中的每一個節點,都提供了兩個 TCP 端口:
7000
節點的集羣端口爲
17000
。
集羣端口只用於節點之間的通訊,如搭建集羣、增減節點、故障轉移等操做時節點間的通訊;不要使用客戶端鏈接集羣接口。爲了保證集羣能夠正常工做,在配置防火牆時,要同時開啓普通端口和集羣端口。
節點間通訊,按照通訊協議能夠分爲幾種類型:單對單、廣播、Gossip 協議等。重點是廣播和 Gossip 的對比。
集羣中的節點採用 固定頻率(每秒10次) 的 定時任務 進行通訊相關的工做:判斷是否須要發送消息及消息類型、肯定接收節點、發送消息等。若是集羣狀態發生了變化,如增減節點、槽狀態變動,經過節點間的通訊,全部節點會很快得知整個集羣的狀態,使集羣收斂。
節點間發送的消息主要分爲 5
種:meet 消息
、ping 消息
、pong 消息
、fail 消息
、publish 消息
。不一樣的消息類型,通訊協議、發送的頻率和時機、接收節點的選擇等是不一樣的:
CLUSTER MEET
命令時,會向新加入的節點發送
MEET
消息,請求新節點加入到當前集羣;新節點收到 MEET 消息後會回覆一個
PONG
消息。
PING
消息,接收者收到消息後會回覆一個
PONG
消息。
PING 消息的內容是自身節點和部分其餘節點的狀態信息,做用是彼此交換信息,以及檢測節點是否在線。
PING
消息使用 Gossip 協議發送,接收節點的選擇兼顧了收斂速度和帶寬成本,
具體規則以下:(1)隨機找 5 個節點,在其中選擇最久沒有通訊的 1 個節點;(2)掃描節點列表,選擇最近一次收到
PONG
消息時間大於
cluster_node_timeout / 2
的全部節點,防止這些節點長時間未更新。
PONG
消息封裝了自身狀態數據。能夠分爲兩種:
第一種 是在接到
MEET/PING
消息後回覆的
PONG
消息;
第二種 是指節點向集羣廣播
PONG
消息,這樣其餘節點能夠獲知該節點的最新信息,例如故障恢復後新的主節點會廣播
PONG
消息。
FAIL
狀態時,會向集羣廣播這一
FAIL
消息;接收節點會將這一
FAIL
消息保存起來,便於後續的判斷。
PUBLISH
命令後,會先執行該命令,而後向集羣廣播這一消息,接收節點也會執行該
PUBLISH
命令。
節點須要專門的數據結構來存儲集羣的狀態。所謂集羣的狀態,是一個比較大的概念,包括:集羣是否處於上線狀態、集羣中有哪些節點、節點是否可達、節點的主從狀態、槽的分佈……
節點爲了存儲集羣狀態而提供的數據結構中,最關鍵的是 clusterNode
和 clusterState
結構:前者記錄了一個節點的狀態,後者記錄了集羣做爲一個總體的狀態。
clusterNode
結構保存了 一個節點的當前狀態,包括建立時間、節點 id、ip 和端口號等。每一個節點都會用一個 clusterNode
結構記錄本身的狀態,併爲集羣內全部其餘節點都建立一個 clusterNode
結構來記錄節點狀態。
下面列舉了 clusterNode
的部分字段,並說明了字段的含義和做用:
typedef struct clusterNode {
//節點建立時間
mstime_t ctime;
//節點id
char name[REDIS_CLUSTER_NAMELEN];
//節點的ip和端口號
char ip[REDIS_IP_STR_LEN];
int port;
//節點標識:整型,每一個bit都表明了不一樣狀態,如節點的主從狀態、是否在線、是否在握手等
int flags;
//配置紀元:故障轉移時起做用,相似於哨兵的配置紀元
uint64_t configEpoch;
//槽在該節點中的分佈:佔用16384/8個字節,16384個比特;每一個比特對應一個槽:比特值爲1,則該比特對應的槽在節點中;比特值爲0,則該比特對應的槽不在節點中
unsigned char slots[16384/8];
//節點中槽的數量
int numslots;
…………
} clusterNode;
複製代碼
除了上述字段,clusterNode
還包含節點鏈接、主從複製、故障發現和轉移須要的信息等。
clusterState
結構保存了在當前節點視角下,集羣所處的狀態。主要字段包括:
typedef struct clusterState {
//自身節點
clusterNode *myself;
//配置紀元
uint64_t currentEpoch;
//集羣狀態:在線仍是下線
int state;
//集羣中至少包含一個槽的節點數量
int size;
//哈希表,節點名稱->clusterNode節點指針
dict *nodes;
//槽分佈信息:數組的每一個元素都是一個指向clusterNode結構的指針;若是槽尚未分配給任何節點,則爲NULL
clusterNode *slots[16384];
…………
} clusterState;
複製代碼
除此以外,clusterState
還包括故障轉移、槽遷移等須要的信息。
更多關於集羣內容請自行閱讀《Redis 設計與實現》,其中有更多細節方面的介紹 - redisbook.com/
本文已收錄至個人 Github 程序員成長系列 【More Than Java】,學習,不止 Code,歡迎 star:github.com/wmyskxz/Mor… 我的公衆號 :wmyskxz, 我的獨立域名博客:wmyskxz.com,堅持原創輸出,下方掃碼關注,2020,與您共同成長!
很是感謝各位人才能 看到這裏,若是以爲本篇文章寫得不錯,以爲 「我沒有三顆心臟」有點東西 的話,求點贊,求關注,求分享,求留言!
創做不易,各位的支持和承認,就是我創做的最大動力,咱們下篇文章見!
本文使用 mdnice 排版