Redis(5.0) 叢集搭建
1、為了達到redis資料庫的高可用,所以需要在單機的基礎上建立叢集,首先了解下它的叢集模式,大概有以下幾種:
1、主從複製 2、哨兵模式 3、Redis官方提供的Cluster叢集模式(服務端) 4、Jedis sharding叢集(客戶端sharding) 5、利用中介軟體代理,比如豌豆莢的codis等 複製程式碼
2、簡單分析一下原理以及優缺點
2.1、主從複製(Master-Slave Replication):
主從複製原理:
-
從伺服器連線主伺服器,傳送SYNC命令;
-
主伺服器接收到SYNC命名後,開始執行BGSAVE命令生成RDB檔案並使用緩衝區記錄此後執行的所有寫命令;
-
主伺服器BGSAVE執行完後,向所有從伺服器傳送快照檔案,並在傳送期間繼續記錄被執行的寫命令;
-
從伺服器收到快照檔案後丟棄所有舊資料,載入收到的快照;
-
主伺服器快照發送完畢後開始向從伺服器傳送緩衝區中的寫命令;
-
從伺服器完成對快照的載入,開始接收命令請求,並執行來自主伺服器緩衝區的寫命令;(從伺服器初始化完成)
-
主伺服器每執行一個寫命令就會向從伺服器傳送相同的寫命令,從伺服器接收並執行收到的寫命令(從伺服器初始化完成後的操作)
-
一個master可以擁有多個slave,但是一個slave只能對應一個master
優點:
-
支援主從複製,主機會自動將資料同步到從機,可以進行讀寫分離
-
為了分載Master的讀操作壓力,Slave伺服器可以為客戶端提供只讀操作的服務,寫服務仍然必須由Master來完成
-
Slave同樣可以接受其它Slaves的連線和同步請求,這樣可以有效的分載Master的同步壓力。
-
Master Server是以非阻塞的方式為Slaves提供服務。所以在Master-Slave同步期間,客戶端仍然可以提交查詢或修改請求。
-
Slave Server同樣是以非阻塞的方式完成資料同步。在同步期間,如果有客戶端提交查詢請求,Redis則返回同步之前的資料 缺點:
-
Redis不具備自動容錯和恢復功能,主機從機的宕機都會導致前端部分讀寫請求失敗,需要等待機器重啟或者手動切換前端的IP才能恢復。
-
主機宕機,宕機前有部分資料未能及時同步到從機,切換IP後還會引入資料不一致的問題,降低了系統的可用性。
-
Redis較難支援線上擴容,在叢集容量達到上限時線上擴容會變得很複雜。
2.2、哨兵模式:
當主伺服器中斷服務後,可以將一個從伺服器升級為主伺服器,以便繼續提供服務,但是這個過程需要人工手動來操作。 為此,Redis 2.8中提供了哨兵工具來實現自動化的系統監控和故障恢復功能。
哨兵的作用就是監控Redis系統的執行狀況。它的功能包括以下兩個。
(1)監控主伺服器和從伺服器是否正常執行。 (2)主伺服器出現故障時自動將從伺服器轉換為主伺服器。 複製程式碼
哨兵的工作方式:
- 每個Sentinel(哨兵)程序以每秒鐘一次的頻率向整個叢集中的Master主伺服器,Slave從伺服器以及其他Sentinel(哨兵)程序傳送一個 PING 命令。
- 如果一個例項(instance)距離最後一次有效回覆 PING 命令的時間超過 down-after-milliseconds 選項所指定的值, 則這個例項會被 Sentinel(哨兵)程序標記為主觀下線(SDOWN)
- 如果一個Master主伺服器被標記為主觀下線(SDOWN),則正在監視這個Master主伺服器的所有 Sentinel(哨兵)程序要以每秒一次的頻率確認Master主伺服器的確進入了主觀下線狀態
- 當有足夠數量的 Sentinel(哨兵)程序(大於等於配置檔案指定的值)在指定的時間範圍內確認Master主伺服器進入了主觀下線狀態(SDOWN), 則Master主伺服器會被標記為客觀下線(ODOWN)
- 在一般情況下, 每個 Sentinel(哨兵)程序會以每 10 秒一次的頻率向叢集中的所有Master主伺服器、Slave從伺服器傳送 INFO 命令。
- 當Master主伺服器被 Sentinel(哨兵)程序標記為客觀下線(ODOWN)時,Sentinel(哨兵)程序向下線的 Master主伺服器的所有 Slave從伺服器傳送 INFO 命令的頻率會從 10 秒一次改為每秒一次。
- 若沒有足夠數量的 Sentinel(哨兵)程序同意 Master主伺服器下線, Master主伺服器的客觀下線狀態就會被移除。若 Master主伺服器重新向 Sentinel(哨兵)程序傳送 PING 命令返回有效回覆,Master主伺服器的主觀下線狀態就會被移除。
優點:
- 哨兵模式是基於主從模式的,所有主從的優點,哨兵模式都具有。
- 主從可以自動切換,系統更健壯,可用性更高。
缺點:
- Redis較難支援線上擴容,在叢集容量達到上限時線上擴容會變得很複雜。
2.3、Redis官方 Cluster叢集模式
redis的哨兵模式基本已經可以實現高可用,讀寫分離 ,但是在這種模式下每臺redis伺服器都儲存相同的資料,很浪費記憶體,所以在redis3.0上加入了cluster模式,實現的redis的分散式儲存,也就是說每臺redis節點上儲存不同的內容。
Redis-Cluster採用無中心結構,它的特點如下:
-
所有的redis節點彼此互聯(PING-PONG機制),內部使用二進位制協議優化傳輸速度和頻寬。
-
節點的fail是通過叢集中超過半數的節點檢測失效時才生效。
-
客戶端與redis節點直連,不需要中間代理層.客戶端不需要連線叢集所有節點,連線叢集中任何一個可用節點即可。
工作方式:
在redis的每一個節點上,都有這麼兩個東西,一個是插槽(slot),它的的取值範圍是:0-16383。還有一個就是cluster,可以理解為是一個叢集管理的外掛。當我們的存取的key到達的時候,redis會根據crc16的演算法得出一個結果,然後把結果對 16384 求餘數,這樣每個 key 都會對應一個編號在 0-16383 之間的雜湊槽,通過這個值,去找到對應的插槽所對應的節點,然後直接自動跳轉到這個對應的節點上進行存取操作。
為了保證高可用,redis-cluster叢集引入了主從模式,一個主節點對應一個或者多個從節點,當主節點宕機的時候,就會啟用從節點。當其它主節點ping一個主節點A時,如果半數以上的主節點與A通訊超時,那麼認為主節點A宕機了。 如果主節點A和它的從節點A1都宕機了,那麼該叢集就無法再提供服務了。
2.4、Jedis sharding叢集
Redis Sharding可以說是在Redis cluster出來之前業界普遍的採用方式,其主要思想是採用hash演算法將儲存資料的key進行hash雜湊,這樣特定的key會被定為到特定的節點上。
慶幸的是,Java Redis客戶端驅動Jedis已支援Redis Sharding功能,即ShardedJedis以及結合快取池的ShardedJedisPool
Jedis的Redis Sharding實現具有如下特點:
- 採用一致性雜湊演算法,將key和節點name同時hashing,然後進行對映匹配,採用的演算法是MURMUR_HASH。採用一致性雜湊而不是採用簡單類似雜湊求模對映的主要原因是當增加或減少節點時,不會產生由於重新匹配造成的rehashing。一致性雜湊隻影響相鄰節點key分配,影響量小。
- 為了避免一致性雜湊隻影響相鄰節點造成節點分配壓力,ShardedJedis會對每個Redis節點根據名字(沒有,Jedis會賦予預設名字)會虛擬化出160個虛擬節點進行雜湊。根據權重weight,也可虛擬化出160倍數的虛擬節點。用虛擬節點做對映匹配,可以在增加或減少Redis節點時,key在各Redis節點移動再分配更均勻,而不是隻有相鄰節點受影響。
- ShardedJedis支援keyTagPattern模式,即抽取key的一部分keyTag做sharding,這樣通過合理命名key,可以將一組相關聯的key放入同一個Redis節點,這在避免跨節點訪問相關資料時很重要。 當然,Redis Sharding這種輕量靈活方式必然在叢集其它能力方面做出妥協。比如擴容,當想要增加Redis節點時,儘管採用一致性雜湊,畢竟還是會有key匹配不到而丟失,這時需要鍵值遷移。 作為輕量級客戶端sharding,處理Redis鍵值遷移是不現實的,這就要求應用層面允許Redis中資料丟失或從後端資料庫重新載入資料。但有些時候,擊穿快取層,直接訪問資料庫層,會對系統訪問造成很大壓力。
2.5、利用中介軟體代理 中介軟體的作用是將我們需要存入redis中的資料的key通過一套演算法計算得出一個值。然後根據這個值找到對應的redis節點,將這些資料存在這個redis的節點中。
常用的中介軟體有這幾種
- Twemproxy
- Codis
- nginx
3、幾種叢集模式大概有了瞭解,現在搭建開始叢集
由於我使用了redis5.0, 搭建過程中也是遇到了一個坑,redis5.0使用redis-cli作為建立叢集的命令,使用c語言實現,不再使用ruby語言,redis3.0使用redis-trib.rb建立叢集,且需要安裝ruby。
gcc的環境之前已經安裝過了,現在不需要處理,直接下一步。 3.1、將之前安裝好的redis檔案,複製到一個新的資料夾redis-cluster下 ,並在新資料夾下複製成6份。
注意要將.rdb和.aof字尾的持久化檔案刪除,如果有的話。

這裡就不考慮3.0 的搭建方式了
3.2、將六個節點的redis.conf配置檔案按照如下進行修改
如果是同一臺主機的話,埠必須不同。不同主機可以相同。 我這裡使用同一臺主機,埠:7001-7006。
daemonize yes port **** #配置自己的節點埠 cluster-enabled yes # 開啟叢集 複製程式碼
3.3啟動、關閉指令碼
啟動指令碼:
cd redis1 ./src/redis-server redis.conf cd .. cd redis2 ./src/redis-server redis.conf cd .. cd redis3 ./src/redis-server redis.conf cd .. cd redis4 ./src/redis-server redis.conf cd .. cd redis5 ./src/redis-server redis.conf cd .. cd redis6 ./src/redis-server redis.conf cd .. 複製程式碼
關閉指令碼:
./redis1/redis-cli -p 7001 shutdown ./redis1/redis-cli -p 7002 shutdown ./redis1/redis-cli -p 7003 shutdown ./redis1/redis-cli -p 7004 shutdown ./redis1/redis-cli -p 7005 shutdown ./redis1/redis-cli -p 7006 shutdown 複製程式碼
3.4、修改許可權
chmod 777 start-all.sh stop-all.sh 複製程式碼
3.5、啟動節點
./start-all.sh # 檢視redis程序 ps -aux | grep redis 複製程式碼

3.6、建立叢集
redis-cli --cluster create192.0.0.179:7001 192.0.0.179:7002 192.0.0.179:7003 192.0.0.179:7004 192.0.0.179:7005 192.0.0.179:7006 --cluster-replicas 1 複製程式碼

可以看到6個節點配分配成3個主節點,3個從節點。
3.7、查詢叢集資訊
叢集建立成功登陸任意redis結點查詢叢集中的節點情況。
客戶端以叢集方式登陸:
redis-cli -c -h 192.0.0.179 -p 7001 #其中-c表示以叢集方式連線redis,-h指定ip地址,-p指定埠號 複製程式碼
查詢叢集結點資訊:
cluster nodes 複製程式碼

3.8、測試
192.0.0.179:7001> set a 123456 -> Redirected to slot [15495] located at 192.0.0.179:7003 OK 192.0.0.179:7003> get a "123456" 192.0.0.179:7003> get a "123456" 192.0.0.179:7003> set b 44444444 -> Redirected to slot [3300] located at 192.0.0.179:7001 OK 192.0.0.179:7001> get b "44444444" 192.0.0.179:7001> 複製程式碼
可以看到redis 叢集會根據計算為我們分配hash槽,以分配不同的節點
3.9、使用jedis連線
package com.pc.jedis.test; import java.util.HashSet; import java.util.Set; import redis.clients.jedis.HostAndPort; import redis.clients.jedis.JedisCluster; public class JedisClusterTest { public static void main(String[] args) { // 建立並填充節點資訊 Set<HostAndPort> nodes = new HashSet<>(); nodes.add(new HostAndPort("192.0..0.179", 7001)); nodes.add(new HostAndPort("192.0..0.179", 7002)); nodes.add(new HostAndPort("192.0..0.179", 7003)); nodes.add(new HostAndPort("192.0..0.179", 7004)); nodes.add(new HostAndPort("192.0..0.179", 7005)); nodes.add(new HostAndPort("192.0..0.179", 7006)); // 建立JedisCluster物件 JedisCluster jedisCluster = new JedisCluster(nodes); // 使用jedisCluster操作redis String key = "clusterTest"; String setResult = jedisCluster.set(key, "redis叢集搭建"); System.out.println(setResult); String getResult = jedisCluster.get(key); System.out.println(getResult); // 關閉jedisCluster(程式執行完後才能關閉,內部封裝了連線池) jedisCluster.close(); } } 複製程式碼