Redis(9)——史上最強【集羣】入門實踐教程

1、Redis 集羣概述

Redis 主從複製

目前 爲止,咱們所學習的 Redis 都是 單機版 的,這也就意味着一旦咱們所依賴的 Redis 服務宕機了,咱們的主流程也會受到必定的影響,這固然是咱們不可以接受的。html

因此一開始咱們的想法是:搞一臺備用機。這樣咱們就能夠在一臺服務器出現問題的時候切換動態地到另外一臺去:node

幸運的是,兩個節點數據的同步咱們可使用 Redis 的 主從同步 功能幫助到咱們,這樣一來,有個備份,內心就踏實多了。git

Redis 哨兵

後來由於某種神祕力量,Redis 老會在莫名其妙的時間點出問題 (好比半夜 2 點),我總不能 24 小時時刻守在電腦旁邊切換節點吧,因而另外一個想法又開始了:給全部的節點找一個 "管家",自動幫我監聽照顧節點的狀態並切換:程序員

這大概就是 Redis 哨兵 (Sentinel) 的簡單理解啦。什麼?管家宕機了怎麼辦?相較於有大量請求的 Redis 服務來講,管家宕機的機率就要小得多啦.. 若是真的宕機了,咱們也能夠直接切換成當前可用的節點保證可用..github

Redis 集羣化

好了,經過上面的一些解決方案咱們對 Redis 的 穩定性 稍微有了一些底氣了,但單臺節點的計算能力始終有限,所謂人多力量大,若是咱們把 多個節點組合一個可用的工做節點,那就大大增長了 Redis 的 高可用、可擴展、分佈式、容錯 等特性:web

2、主從複製

主從複製,是指將一臺 Redis 服務器的數據,複製到其餘的 Redis 服務器。前者稱爲 主節點(master),後者稱爲 從節點(slave)。且數據的複製是 單向 的,只能由主節點到從節點。Redis 主從複製支持 主從同步從從同步 兩種,後者是 Redis 後續版本新增的功能,以減輕主節點的同步負擔。redis

主從複製主要的做用

  • 數據冗餘: 主從複製實現了數據的熱備份,是持久化以外的一種數據冗餘方式。
  • 故障恢復: 當主節點出現問題時,能夠由從節點提供服務,實現快速的故障恢復 (其實是一種服務的冗餘)
  • 負載均衡: 在主從複製的基礎上,配合讀寫分離,能夠由主節點提供寫服務,由從節點提供讀服務 (即寫 Redis 數據時應用鏈接主節點,讀 Redis 數據時應用鏈接從節點),分擔服務器負載。尤爲是在寫少讀多的場景下,經過多個從節點分擔讀負載,能夠大大提升 Redis 服務器的併發量。
  • 高可用基石: 除了上述做用之外,主從複製仍是哨兵和集羣可以實施的 基礎,所以說主從複製是 Redis 高可用的基礎。

快速體驗

Redis 中,用戶能夠經過執行 SLAVEOF 命令或者設置 slaveof 選項,讓一個服務器去複製另外一個服務器,如下三種方式是 徹底等效 的:算法

  • 配置文件:在從服務器的配置文件中加入: slaveof <masterip> <masterport>
  • 啓動命令:redis-server 啓動命令後加入 --slaveof <masterip> <masterport>
  • 客戶端命令:Redis 服務器啓動後,直接經過客戶端執行命令: slaveof <masterip> <masterport>,讓該 Redis 實例成爲從節點。

須要注意的是:主從複製的開啓,徹底是在從節點發起的,不須要咱們在主節點作任何事情。數組

第一步:本地啓動兩個節點

在正確安裝好 Redis 以後,咱們可使用 redis-server --port <port> 的方式指定建立兩個不一樣端口的 Redis 實例,例如,下方我分別建立了一個 63796380 的兩個 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
複製代碼

第三步:觀察效果

下面咱們來驗證一下,主節點的數據是否會複製到從節點之中:

  • 先在 從節點 中查詢一個 不存在 的 key:
127.0.0.1:6380> GET mykey
(nil)
複製代碼
  • 再在 主節點 中添加這個 key:
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 命令是一個很是耗費資源的操做

每次執行 SYNC 命令,主從服務器須要執行以下動做:

  1. 主服務器 須要執行 BGSAVE 命令來生成 RDB 文件,這個生成操做會 消耗 主服務器大量的 CPU、內存和磁盤 I/O 的資源
  2. 主服務器 須要將本身生成的 RDB 文件 發送給從服務器,這個發送操做會 消耗 主服務器 大量的網絡資源 (帶寬和流量),並對主服務器響應命令請求的時間產生影響;
  3. 接收到 RDB 文件的 從服務器 須要載入主服務器發來的 RBD 文件,而且在載入期間,從服務器 會由於阻塞而沒辦法處理命令請求

特別是當出現 斷線重複制 的狀況是時,爲了讓從服務器補足斷線時確實的那一小部分數據,卻要執行一次如此耗資源的 SYNC 命令,顯然是不合理的。

PSYNC 命令的引入

因此在 Redis 2.8 中引入了 PSYNC 命令來代替 SYNC,它具備兩種模式:

  1. 全量複製: 用於初次複製或其餘沒法進行部分複製的狀況,將主節點中的全部數據都發送給從節點,是一個很是重型的操做;
  2. 部分複製: 用於網絡中斷等狀況後的複製,只將 中斷期間主節點執行的寫命令 發送給從節點,與全量複製相比更加高效。 須要注意 的是,若是網絡中斷時間過長,致使主節點沒有可以完整地保存中斷期間執行的寫命令,則沒法進行部分複製,仍使用全量複製;

部分複製的原理主要是靠主從節點分別維護一個 複製偏移量,有了這個偏移量以後斷線重連以後一比較,以後就能夠僅僅把從服務器斷線以後確實的這部分數據給補回來了。

更多的詳細內容能夠參考下方 參考資料 3

3、Redis Sentinel 哨兵

上圖 展現了一個典型的哨兵架構圖,它由兩部分組成,哨兵節點和數據節點:

  • 哨兵節點: 哨兵系統由一個或多個哨兵節點組成,哨兵節點是特殊的 Redis 節點,不存儲數據;
  • 數據節點: 主節點和從節點都是數據節點;

在複製的基礎上,哨兵實現了 自動化的故障恢復 功能,下方是官方對於哨兵功能的描述:

  • 監控(Monitoring): 哨兵會不斷地檢查主節點和從節點是否運做正常。
  • 自動故障轉移(Automatic failover):主節點 不能正常工做時,哨兵會開始 自動故障轉移操做,它會將失效主節點的其中一個 從節點升級爲新的主節點,並讓其餘從節點改成複製新的主節點。
  • 配置提供者(Configuration provider): 客戶端在初始化時,經過鏈接哨兵來得到當前 Redis 服務的主節點地址。
  • 通知(Notification): 哨兵能夠將故障轉移的結果發送給客戶端。

其中,監控和自動故障轉移功能,使得哨兵能夠及時發現主節點故障並完成轉移。而配置提供者和通知功能,則須要在與客戶端的交互中才能體現。

快速體驗

第一步:建立主從節點配置文件並啓動

正確安裝好 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 新的主節點。
  • 對於哨兵節點: 除了主從節點信息的變化,紀元(epoch) (記錄當前集羣狀態的參數) 也會變化,紀元相關的參數都 +1 了。

客戶端訪問哨兵系統代碼演示

上面咱們在 快速體驗 中主要感覺到了服務端本身對於當前主從節點的自動化治理,下面咱們以 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 的構造器中,進行了相關的工做;主要包括如下兩點:

  1. 遍歷哨兵節點,獲取主節點信息: 遍歷哨兵節點,經過其中一個哨兵節點 + masterName 得到主節點的信息;該功能是經過調用哨兵節點的 sentinel get-master-addr-by-name 命令實現;
  2. 增長對哨兵的監聽: 這樣當發生故障轉移時,客戶端即可以收到哨兵的通知,從而完成主節點的切換。具體作法是:利用 Redis 提供的 發佈訂閱 功能,爲每個哨兵節點開啓一個單獨的線程,訂閱哨兵節點的 + switch-master 頻道,當收到消息時,從新初始化鏈接池。

新的主服務器是怎樣被挑選出來的?

故障轉移操做的第一步 要作的就是在已下線主服務器屬下的全部從服務器中,挑選出一個狀態良好、數據完整的從服務器,而後向這個從服務器發送 slaveof no one 命令,將這個從服務器轉換爲主服務器。可是這個從服務器是怎麼樣被挑選出來的呢?

簡單來講 Sentinel 使用如下規則來選擇新的主服務器:

  1. 在失效主服務器屬下的從服務器當中, 那些被標記爲主觀下線、已斷線、或者最後一次回覆 PING 命令的時間大於五秒鐘的從服務器都會被 淘汰
  2. 在失效主服務器屬下的從服務器當中, 那些與失效主服務器鏈接斷開的時長超過 down-after 選項指定的時長十倍的從服務器都會被 淘汰
  3. 經歷了以上兩輪淘汰以後 剩下來的從服務器中, 咱們選出 複製偏移量(replication offset)最大 的那個 從服務器 做爲新的主服務器;若是複製偏移量不可用,或者從服務器的複製偏移量相同,那麼 帶有最小運行 ID 的那個從服務器成爲新的主服務器。

4、Redis 集羣

上圖 展現了 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 指令第一個參數 3999key 對應的槽位編號,後面是目標節點地址,MOVED 命令前面有一個減號,表示這是一個錯誤的消息。客戶端在收到 MOVED 指令後,就當即糾正本地的 槽位映射表,那麼下一次再訪問 key 時就可以到正確的地方去獲取了。

集羣的主要做用

  1. 數據分區: 數據分區 (或稱數據分片) 是集羣最核心的功能。集羣將數據分散到多個節點, 一方面 突破了 Redis 單機內存大小的限制, 存儲容量大大增長另外一方面 每一個主節點均可以對外提供讀服務和寫服務, 大大提升了集羣的響應能力。Redis 單機內存大小受限問題,在介紹持久化和主從複製時都有說起,例如,若是單機內存太大, bgsavebgrewriteaoffork 操做可能致使主進程阻塞,主從環境下主機切換時可能致使從節點長時間沒法提供服務,全量複製階段主節點的複製緩衝區可能溢出……
  2. 高可用: 集羣支持主從複製和主節點的 自動故障轉移 (與哨兵相似),當任一節點發生故障時,集羣仍然能夠對外提供服務。

快速體驗

第一步:建立集羣節點配置文件

首先咱們找一個地方建立一個名爲 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)

第二步:分別啓動 6 個 Redis 實例

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 值,確數據在環上的位置,而後今後位置沿順時針行走,找到的第一臺服務器就是其應該映射到的服務器:

與哈希取餘分區相比,一致性哈希分區將 增減節點的影響限制在相鄰節點。以上圖爲例,若是在 node1node2 之間增長 node5,則只有 node2 中的一部分數據會遷移到 node5;若是去掉 node2,則原 node2 中的數據只會遷移到 node4 中,只有 node4 會受影響。

一致性哈希分區的主要問題在於,當 節點數量較少 時,增長或刪減節點,對單個節點的影響可能很大,形成數據的嚴重不平衡。仍是以上圖爲例,若是去掉 node2node4 中的數據由總數據的 1/4 左右變爲 1/2 左右,與其餘節點相比負載太高。

方案三:帶有虛擬節點的一致性哈希分區

該方案在 一致性哈希分區的基礎上,引入了 虛擬節點 的概念。Redis 集羣使用的即是該方案,其中的虛擬節點稱爲 槽(slot)。槽是介於數據和實際節點之間的虛擬概念,每一個實際節點包含必定數量的槽,每一個槽包含哈希值在必定範圍內的數據。

在使用了槽的一致性哈希分區中,槽是數據管理和遷移的基本單位。槽 解耦數據和實際節點 之間的關係,增長或刪除節點對系統的影響很小。仍以上圖爲例,系統中有 4 個實際節點,假設爲其分配 16 個槽(0-15);

  • 槽 0-3 位於 node1;4-7 位於 node2;以此類推....

若是此時刪除 node2,只須要將槽 4-7 從新分配便可,例如槽 4-5 分配給 node1,槽 6 分配給 node3,槽 7 分配給 node4;能夠看出刪除 node2 後,數據在其餘節點的分佈仍然較爲均衡。

節點通訊機制簡析

集羣的創建離不開節點之間的通訊,例如咱們上訪在 快速體驗 中剛啓動六個集羣節點以後經過 redis-cli 命令幫助咱們搭建起來了集羣,實際上背後每一個集羣之間的兩兩鏈接是經過了 CLUSTER MEET <ip> <port> 命令發送 MEET 消息完成的,下面咱們展開詳細說說。

兩個端口

哨兵系統 中,節點分爲 數據節點哨兵節點:前者存儲數據,後者實現額外的控制功能。在 集羣 中,沒有數據節點與非數據節點之分:全部的節點都存儲數據,也都參與集羣狀態的維護。爲此,集羣中的每一個節點,都提供了兩個 TCP 端口:

  • 普通端口: 即咱們在前面指定的端口 (7000等)。普通端口主要用於爲客戶端提供服務 (與單機節點相似);但在節點間數據遷移時也會使用。
  • 集羣端口: 端口號是普通端口 + 10000 (10000是固定值,沒法改變),如 7000 節點的集羣端口爲 17000集羣端口只用於節點之間的通訊,如搭建集羣、增減節點、故障轉移等操做時節點間的通訊;不要使用客戶端鏈接集羣接口。爲了保證集羣能夠正常工做,在配置防火牆時,要同時開啓普通端口和集羣端口。

Gossip 協議

節點間通訊,按照通訊協議能夠分爲幾種類型:單對單、廣播、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 命令。

數據結構簡析

節點須要專門的數據結構來存儲集羣的狀態。所謂集羣的狀態,是一個比較大的概念,包括:集羣是否處於上線狀態、集羣中有哪些節點、節點是否可達、節點的主從狀態、槽的分佈……

節點爲了存儲集羣狀態而提供的數據結構中,最關鍵的是 clusterNodeclusterState 結構:前者記錄了一個節點的狀態,後者記錄了集羣做爲一個總體的狀態。

clusterNode 結構

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 結構

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/

相關閱讀

  1. Redis(1)——5種基本數據結構 - www.wmyskxz.com/2020/02/28/…
  2. Redis(2)——跳躍表 - www.wmyskxz.com/2020/02/29/…
  3. Redis(3)——分佈式鎖深刻探究 - www.wmyskxz.com/2020/03/01/…
  4. Reids(4)——神奇的HyperLoglog解決統計問題 - www.wmyskxz.com/2020/03/02/…
  5. Redis(5)——億級數據過濾和布隆過濾器 - www.wmyskxz.com/2020/03/11/…
  6. Redis(6)——GeoHash查找附近的人 www.wmyskxz.com/2020/03/12/…
  7. Redis(7)——持久化【一文了解】 - www.wmyskxz.com/2020/03/13/…
  8. Redis(8)——發佈/訂閱與Stream - www.wmyskxz.com/2020/03/15/…

參考資料

  1. 《Redis 設計與實現》 | 黃健宏 著 - redisbook.com/
  2. 《Redis 深度歷險》 | 錢文品 著 - book.douban.com/subject/303…
  3. 深刻學習Redis(3):主從複製 - www.cnblogs.com/kismetv/p/9…
  4. Redis 主從複製 原理與用法 - blog.csdn.net/Stubborn_Co…
  5. 深刻學習Redis(4):哨兵 - www.cnblogs.com/kismetv/p/9…
  6. Redis 5 以後版本的高可用集羣搭建 - www.jianshu.com/p/8045b92fa…
  • 本文已收錄至個人 Github 程序員成長系列 【More Than Java】,學習,不止 Code,歡迎 star:github.com/wmyskxz/Mor…
  • 我的公衆號 :wmyskxz, 我的獨立域名博客:wmyskxz.com,堅持原創輸出,下方掃碼關注,2020,與您共同成長!

很是感謝各位人才能 看到這裏,若是以爲本篇文章寫得不錯,以爲 「我沒有三顆心臟」有點東西 的話,求點贊,求關注,求分享,求留言!

創做不易,各位的支持和承認,就是我創做的最大動力,咱們下篇文章見!

本文使用 mdnice 排版

相關文章
相關標籤/搜索