1. 程式人生 > 資料庫 >Java中高階核心知識全面解析——Redis(叢集【概述{主從複製、哨兵、叢集化}、資料分割槽方案、節點通訊機制、資料結構簡析】)5

Java中高階核心知識全面解析——Redis(叢集【概述{主從複製、哨兵、叢集化}、資料分割槽方案、節點通訊機制、資料結構簡析】)5

目錄

一、[叢集]入門實踐教程

1.Redis 叢集概述

1)Redis 主從複製

到目前為止,我們所學習的 Redis 都是 單機版 的,這也就意味著一旦我們所依賴的 Redis 服務宕機了,我們的主流程也會受到一定的影響,這當然是我們不能夠接受的。

所以一開始我們的想法是:搞一臺備用機。這樣我們就可以在一臺伺服器出現問題的時候切換動態地到另一臺去:

幸運的是,兩個節點資料的同步我們可以使用 Redis 的 主從同步 功能幫助到我們,這樣一來,有個備份,心裡就踏實多了。

2)Redis 哨兵

後來因為某種神祕力量,Redis 老會在莫名其妙的時間點出問題 (比如半夜 2 點

),我總不能 24 小時時刻守在電腦旁邊切換節點吧,於是另一個想法又開始了:給所有的節點找一個**“管家”,自動幫我監聽照顧節點的狀態並切換:

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

3)Redis 叢集化

好了,通過上面的一些解決方案我們對 Redis 的 穩定性 稍微有了一些底氣了,但單臺節點的計算能力始終有限,所謂人多力量大,如果我們把 多個節點組合 成 一個可用的工作節點,那就大大增加了Redis 的 高可用

可擴充套件分散式容錯 等特性:

2.主從複製


主從複製,是指將一臺 Redis 伺服器的資料,複製到其他的 Redis 伺服器。前者稱為 主節點(master),後者稱為 從節點(slave)。且資料的複製是 單向 的,只能由主節點到從節點。Redis 主從複製支援 主從同步從從同步兩種,後者是 Redis 後續版本新增的功能,以減輕主節點的同步負擔。

1)主從複製主要的作用

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

2)快速體驗

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.

3)實現原理簡析


為了節省篇幅,我把主要的步驟都 濃縮 在了上圖中,其實也可以 **簡化成三個階段:準備階段-資料同步階段-命令傳播階段。**下面我們來進行一些必要的說明。

①、身份驗證 | 主從複製安全問題

在上面的 快速體驗 過程中,你會發現 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.Redis Sentinel 哨兵


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

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

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

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

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

1)快速體驗

①、第一步:建立主從節點配置檔案並啟動

正確安裝好 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 檢查一下主從狀態是否正常:(可以看到下方正確地顯示了兩個從節點)


小插曲:
更多阿里、騰訊、美團、京東等一線網際網路大廠Java面試真題;包含:基礎、併發、鎖、JVM、設計模式、資料結構、反射/IO、資料庫、Redis、Spring、訊息佇列、分散式、Zookeeper、Dubbo、Mybatis、Maven、面經等。
更多Java程式設計師技術進階小技巧;例如高效學習(如何學習和閱讀程式碼、面對枯燥和量大的知識)高效溝通(溝通方式及技巧、溝通技術)
更多Java大牛分享的一些職業生涯分享文件


請點選社群,免費獲取


比你優秀的對手在學習,你的仇人在磨刀,你的閨蜜在減肥,隔壁老王在練腰, 我們必須不斷學習,否則我們將被學習者超越!
趁年輕,使勁拼,給未來的自己一個交代!

②、第二步:建立哨兵節點配置檔案並啟動

按照上面同樣的方法,我們給哨兵節點也建立三個配置檔案。(哨兵節點本質上是特殊的 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 了。

2)客戶端訪問哨兵系統程式碼演示

上面我們在 快速體驗 中主要感受到了服務端自己對於當前主從節點的自動化治理,下面我們以 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 頻道,當收到訊息時,重新初始化連線池。

3)新的主伺服器是怎樣被挑選出來的?

故障轉移操作的第一步 要做的就是在已下線主伺服器屬下的所有從伺服器中,挑選出一個狀態良好、資料完整的從伺服器,然後向這個從伺服器傳送 slaveof no one 命令,將這個從伺服器轉換為主伺服器。但是這個從伺服器是怎麼樣被挑選出來的呢?

簡單來說 Sentinel 使用以下規則來選擇新的主伺服器:

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

4.Redis 叢集


上圖 展示了 Redis Cluster 典型的架構圖,叢集中的每一個 Redis 節點都 互相兩兩相連,客戶端任意直連 到叢集中的 任意一臺,就可以對其他 Redis 節點進行 讀寫 的操作。

1)基本原理


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 時就能夠到正確的地方去獲取了。

2)叢集的主要作用

  1. 資料分割槽: 資料分割槽 (或稱資料分片) 是叢集最核心的功能。叢集將資料分散到多個節點,一方面突破了 Redis 單機記憶體大小的限制,儲存容量大大增加另一方面 每個主節點都可以對外提供讀服務和寫服務,大大提高了叢集的響應能力。Redis 單機記憶體大小受限問題,在介紹持久化和主從複製時都有提及,例如,如果單機記憶體太大, bgsavebgrewriteaoffork 操作可能導致主程序阻塞,主從環境下主機切換時可能導致從節點長時間無法提供服務,全量複製階段主節點的複製緩衝區可能溢位……
  2. 高可用: 叢集支援主從複製和主節點的 自動故障轉移與哨兵類似),當任一節點發生故障時,叢集仍然可以對外提供服務。

3)快速體驗

①、第一步:建立叢集節點配置檔案

首先我們找一個地方建立一個名為 redis-cluster 的目錄:


小插曲:
更多阿里、騰訊、美團、京東等一線網際網路大廠Java面試真題;包含:基礎、併發、鎖、JVM、設計模式、資料結構、反射/IO、資料庫、Redis、Spring、訊息佇列、分散式、Zookeeper、Dubbo、Mybatis、Maven、面經等。
更多Java程式設計師技術進階小技巧;例如高效學習(如何學習和閱讀程式碼、面對枯燥和量大的知識)高效溝通(溝通方式及技巧、溝通技術)
更多Java大牛分享的一些職業生涯分享文件


請點選社群,免費獲取


比你優秀的對手在學習,你的仇人在磨刀,你的閨蜜在減肥,隔壁老王在練腰, 我們必須不斷學習,否則我們將被學習者超越!
趁年輕,使勁拼,給未來的自己一個交代!

mkdir -p ~/Desktop/redis-cluster

然後按照上面的方法,建立六個配置檔案,分別命名為:redis_7000.conf / redis_7001.confredis_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>

5)資料分割槽方案簡析

①、方案一:雜湊值 % 節點數

雜湊取餘分割槽思路非常簡單:計算 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 後,資料在其他節點的分佈仍然較為均衡。

6)節點通訊機制簡析

叢集的建立離不開節點之間的通訊,例如我們上訪在 快速體驗 中剛啟動六個叢集節點之後通過 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 命令。

7)資料結構簡析

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

節點為了儲存叢集狀態而提供的資料結構中,最關鍵的是 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 還包括故障轉移、槽遷移等需要的資訊。


參考資料:《Java中高階核心知識全面解析》限量100份,有一些人已經通過我之前的文章獲取了哦!
名額有限先到先得!!!
有想要獲取這份學習資料的同學可以