Redis Cluster高可用叢集
概述
redis cluster叢集是一個由多個主從節點群組成的分布式伺服器群,它具有複制、高可用和分片特
性。
Redis cluster叢集不需要sentinel哨兵節點也能完成節點移除和故障轉移的功能。需要将每個節點
設定成叢集模式,這種叢集模式沒有中心節點,可水準擴充,據官方文檔稱可以線性擴充到
1000節點。
redis cluster叢集的性能和高可用性均優于之前版本的哨兵模式,且叢集配置非常簡單,其架構如下圖所示:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHLxEEROlXWq1UMNpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLycTN2MDO0cTMzITMxkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
叢集的搭建
redis叢集的搭建分兩種方法,一種是原生的方法搭建,一種是使用redis提供的rb腳本搭建。
原生搭建
這裡搭建四個節點的叢集,兩個主節點,兩個從節點,首先還是配置每個redis節點,同樣也是通過redis.conf檔案配置。
配置節點
各個節點大部配置設定置還是和之前主從模式的節點配置差不多。
這裡綁定的ip是我使用的虛拟機的ip,這樣我的本機也是可以通路的
還是盡量減少RDB持久化的發生
同樣如果我們配置了redis的密碼還是要設定密碼,不過不需要配置主機節點
接着開啟叢集模式
配置它的叢集資訊檔案
打開這個配置
這個屬性配置為no,這個配置的意思是在某一個節點出現問題挂掉的時候,如果配置no這個節點不可用,但其他節點可用,如果配置yes,代表整個叢集都不可用,直到故障轉移結束。
啟動節點
- 啟動節點
啟動方式還是和普通節點的啟動一樣
- 節點互通
這時叢集其實還沒有搭建成功,此時這些幾點之間還沒有互相通信,先連接配接其中一個用戶端
我們發現叢集節點中隻有自己
通過下面的指令連通其他的節點
cluster meet ip port
這裡的連接配接時雙向的,互通的,不需要各自和各自的節點手動連接配接,隻要節點之間有其他節點相連都可以自動相連。現在各個節點之間都可以自由通信了,不過,現在的叢集還沒有搭建好,還沒有配置設定槽點。
- 指派槽點
通過下面的節點指派槽點,redis總共有16384個槽點,這些槽點決定了那些資料存在哪些節點中,我們總共有兩個主機節點,是以平分就是每個主機節點8192個槽點。這裡如果手動配置設定那會很慢,可以通過腳本配置設定。
腳本如下
這裡将槽點平分給7000和7001這兩個節點
- 配置設定主從
上面我們看到隻存在主機節點,沒有從節點,是以我們需要配置設定主從,我們需要通過從機的用戶端指定它的主機節點,這裡指定的是節點的id,就是nodes資訊裡最前面的一串字元。
現在我們發現主從已經配置設定好了
- 打開用戶端
現在我們可以通過叢集的方式打開用戶端,隻需要在最後加上
-c
就可以了
現在redis叢集就搭建好了
使用redis提供的腳本
這裡我們會搭建一個6個節點的叢集,3主3從,而且在5.0之後redis提供了另一種搭建的辦法,就不需要借助rb了,這裡也是主要介紹的也是這種方式。
首先配置還是和之前一摸一樣,就不再過多介紹了,我們直接從叢集的搭建開始介紹。
這裡有兩種方式:
-
5.0之前redis提供的rb腳本,不過這種方式需要首先擁有一個rb環境,安裝rb,然後通過rb腳本搭建叢集
(1)
(2)cd /usr/local/redis3/src
./redis-trib.rb create --replicas 1 127.0.0.1:9000 127.0.0.1:9001 127.0.0.1:9002 127.0.0.1:9003 127.0.0.1:9004 127.0.0.1:9005
- 5.0之後redis提供了另外的方式搭建叢集,使用
搭建,下面也會使用此方式搭建叢集。/usr/local/bin/redis-cli --cluster create 192.168.0.104:7000 192.168.0.104:7001 192.168.0.104:7002 192.168.0.104:7003 192.168.0.104:7004 192.168.0.104:7005 --cluster-replicas 1
首先我們已經配置好了6個節點的配置檔案,然後啟動每一個節點。
隻需要執行下面的指令就可以直接搭建叢集(5.0以後),我們可以在幫助中檢視這個指令的各種參數的說明。
我們搭建叢集需要使用的指令就是create指令,這裡需要解釋的是最後
--cluster-replicas 1
這個參數代表的是叢集中主從節點的比率,1代表有多少主機就有多少從機,這裡就是3主3從。
/usr/local/bin/redis-cli -h 192.168.123.19 -p 8000 -a 123456 --cluster create 192.168.123.19:8000 192.168.123.19:8001 192.168.123.19:8002 192.168.123.19:8003 192.168.123.19:8004 192.168.123.19:8005 --cluster-replicas 1
這時叢集就已經搭建好了
叢集伸縮
叢集擴容
對于已經搭建好的叢集我們可能還需要添加節點,這時我們就需要手動對這個叢集擴容,下面我會示範對一個叢集添加一個主節點和它的一個從節點。
同樣有兩種方式實作擴容,一種是原生的,一種是通過redis-cli的指令擴容。
- 原生的文法:
cluster meet ip port
- redis-cli的指令:
add-node 新節點ip 端口 已存在節點ip 端口
下面是示範的通過redis-cli的指令實作叢集擴容
首先還是需要其他需要添加的節點
把節點加入叢集
現在我們需要将從節點加入叢集并且配置設定主從,這裡可以采用先将從節點加入叢集再配置設定叢集,也可以通過redis-cli指令直接将從節點加入叢集并一起配置設定主從,配置設定主從前面已經介紹了原生的方法,這裡介紹redis-cli指令直接配置設定主從節點。
add-node 新節點ip 端口 已存在節點ip 端口 --cluster-slave --cluster-master-id masterID
遷移槽和資料
我們可以動态遷移槽和資料
- 首先開始槽遷移
/usr/local/bin/redis-cli --cluster reshard 192.168.123.19:8000
- 指定遷移的計劃
首先是需要配置設定多少槽
選擇接收的節點
接着就是輸入槽點的源節點,我們配置設定的槽點就是由這些節點得來的,這裡我們可以輸入一個或多個源槽位的節點ID,輸入完了就輸入done表示結束,如果我們需要從所有其他的節點拿到槽位可以直接輸入all,這裡都是平均拿到槽位。
叢集縮容
下線遷移槽
可以通過下面的指令将某個節點的一些槽位遷移給其他的節點
redis-cli --cluster reshard --cluster-from 要遷出節點ID --cluster-to 接收槽節點ID --cluster-slots 遷出槽數量 已存在節點ip 端口
移除節點
可以通過下面的指令将節點移出叢集,注意這裡不僅會将節點移出叢集而且還會關閉這個redis節點
redis-cli --cluster del-node 已存在節點ip:端口 要删除的節點ID
故障轉移
redis叢集的故障轉移和哨兵模式下的故障轉移類似,下面是故障轉移的過程:
- 故障發現: 通過ping/pong消息實作故障發現(不依賴sentinel)
- 故障恢複:
- 檢查從節點的資格,每個從節點檢查與主節點的斷開時間,超過
*cluster-node-timeout
時間取消資格cluster-replica-validity-factor
- 選擇偏移量最大的,替換主節點
- 撤銷以前主節點的槽位,給新的主節點
- 向叢集廣播消息,表明已經替換了故障節點
- 檢查從節點的資格,每個從節點檢查與主節點的斷開時間,超過
叢集用戶端
smart用戶端
- 從叢集中選取一個可運作節點,使用cluster slots初始化槽和節點映射。
- 将cluster slots的結果映射到本地,為每個節點建立jedispool
- 準備執行指令
public class TestRedisCluster {
public static void main(String[] args) {
Logger logger= LoggerFactory.getLogger(TestRedisCluster.class);
Set<HostAndPort> nodesList=new HashSet<>();
nodesList.add(new HostAndPort("192.168.0.104",7000));
nodesList.add(new HostAndPort("192.168.0.104",7001));
nodesList.add(new HostAndPort("192.168.0.104",7002));
nodesList.add(new HostAndPort("192.168.0.104",7003));
nodesList.add(new HostAndPort("192.168.0.104",7004));
nodesList.add(new HostAndPort("192.168.0.104",7005));
// Jedis連接配接池配置
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// 最大空閑連接配接數, 預設8個
jedisPoolConfig.setMaxIdle(200);
// 最大連接配接數, 預設8個
jedisPoolConfig.setMaxTotal(1000);
//最小空閑連接配接數, 預設0
jedisPoolConfig.setMinIdle(100);
// 擷取連接配接時的最大等待毫秒數(如果設定為阻塞時BlockWhenExhausted),如果逾時就抛異常, 小于零:阻塞不确定的時間, 預設-1
jedisPoolConfig.setMaxWaitMillis(3000); // 設定2秒
//對拿到的connection進行validateObject校驗
jedisPoolConfig.setTestOnBorrow(false);
JedisCluster jedisCluster=new JedisCluster(nodesList,jedisPoolConfig);
System.out.println(jedisCluster.mset("k1", "v1", "k2", "v2", "k3", "v3"));
System.out.println(jedisCluster.mget("k1","k2", "k3" ));
// while (true) {
// try {
// String s = UUID.randomUUID().toString();
// jedisCluster.set("k" + s, "v" + s);
// System.out.println(jedisCluster.get("k" + s));
// Thread.sleep(1000);
// }catch (Exception e){
// logger.error(e.getMessage());
// }finally {
if(jedisCluster!=null){
jedisCluster.close();
}
// }
// }
}
}
下面是jedis叢集用戶端可能出現的問題:
- moved重定向:指我們發送指令時,會對發送的key進行crc16算法,得到一個數字,然而我們連接配接的用戶端并不是管理這個數字的範圍,是以會傳回錯誤并告訴你此key應該對應的槽位,然後用戶端需要捕獲此異常,重新發起請求到對應的槽位。
- asx重定向:指在我們送發指令時,對應的用戶端正在遷移槽位中,是以此時我們不能确定這個key是還在舊的節點中還是新的節點中。
- 如果我們使用mset之類的API實作批量操作的時候可能出現問題,因為如果我們操作的資料處于不同的節點中,就會報錯。
redis筆記⑤——redis叢集
下面是jedis叢集用戶端的scan操作的API
public class TestRedisClusterScan {
public static void main(String[] args) {
Logger logger= LoggerFactory.getLogger(TestRedisCluster.class);
Set<HostAndPort> nodesList=new HashSet<>();
nodesList.add(new HostAndPort("192.168.0.104",7000));
nodesList.add(new HostAndPort("192.168.0.104",7001));
nodesList.add(new HostAndPort("192.168.0.104",7002));
nodesList.add(new HostAndPort("192.168.0.104",7003));
nodesList.add(new HostAndPort("192.168.0.104",7004));
nodesList.add(new HostAndPort("192.168.0.104",7005));
// Jedis連接配接池配置
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// 最大空閑連接配接數, 預設8個
jedisPoolConfig.setMaxIdle(200);
// 最大連接配接數, 預設8個
jedisPoolConfig.setMaxTotal(1000);
//最小空閑連接配接數, 預設0
jedisPoolConfig.setMinIdle(100);
// 擷取連接配接時的最大等待毫秒數(如果設定為阻塞時BlockWhenExhausted),如果逾時就抛異常, 小于零:阻塞不确定的時間, 預設-1
jedisPoolConfig.setMaxWaitMillis(3000); // 設定2秒
//對拿到的connection進行validateObject校驗
jedisPoolConfig.setTestOnBorrow(false);
JedisCluster jedisCluster=new JedisCluster(nodesList,jedisPoolConfig);
int hello = JedisClusterCRC16.getCRC16("hello");
System.out.println(hello);
System.out.println(hello%16384);
Jedis connectionFromSlot = jedisCluster.getConnectionFromSlot(hello);
System.out.println(connectionFromSlot);
Map<String, JedisPool> clusterNodes = jedisCluster.getClusterNodes();
Set<Map.Entry<String, JedisPool>> entries = clusterNodes.entrySet();
Iterator<Map.Entry<String, JedisPool>> iterator = entries.iterator();
while (iterator.hasNext()){
Map.Entry<String, JedisPool> next = iterator.next();
String key = next.getKey();
JedisPool jedisPool = clusterNodes.get(key);
System.out.println(key+"--"+jedisPool);
}
}
}