天天看點

redis筆記⑤——redis叢集

Redis Cluster高可用叢集

概述

redis cluster叢集是一個由多個主從節點群組成的分布式伺服器群,它具有複制、高可用和分片特

性。

Redis cluster叢集不需要sentinel哨兵節點也能完成節點移除和故障轉移的功能。需要将每個節點

設定成叢集模式,這種叢集模式沒有中心節點,可水準擴充,據官方文檔稱可以線性擴充到

1000節點。

redis cluster叢集的性能和高可用性均優于之前版本的哨兵模式,且叢集配置非常簡單,其架構如下圖所示:

redis筆記⑤——redis叢集

叢集的搭建

redis叢集的搭建分兩種方法,一種是原生的方法搭建,一種是使用redis提供的rb腳本搭建。

原生搭建

這裡搭建四個節點的叢集,兩個主節點,兩個從節點,首先還是配置每個redis節點,同樣也是通過redis.conf檔案配置。

配置節點

各個節點大部配置設定置還是和之前主從模式的節點配置差不多。

這裡綁定的ip是我使用的虛拟機的ip,這樣我的本機也是可以通路的

redis筆記⑤——redis叢集

還是盡量減少RDB持久化的發生

redis筆記⑤——redis叢集

同樣如果我們配置了redis的密碼還是要設定密碼,不過不需要配置主機節點

redis筆記⑤——redis叢集
redis筆記⑤——redis叢集

接着開啟叢集模式

redis筆記⑤——redis叢集

配置它的叢集資訊檔案

redis筆記⑤——redis叢集

打開這個配置

redis筆記⑤——redis叢集

這個屬性配置為no,這個配置的意思是在某一個節點出現問題挂掉的時候,如果配置no這個節點不可用,但其他節點可用,如果配置yes,代表整個叢集都不可用,直到故障轉移結束。

redis筆記⑤——redis叢集
啟動節點
  1. 啟動節點

啟動方式還是和普通節點的啟動一樣

redis筆記⑤——redis叢集
  1. 節點互通

這時叢集其實還沒有搭建成功,此時這些幾點之間還沒有互相通信,先連接配接其中一個用戶端

redis筆記⑤——redis叢集

我們發現叢集節點中隻有自己

redis筆記⑤——redis叢集

通過下面的指令連通其他的節點

cluster meet ip port
           
redis筆記⑤——redis叢集

這裡的連接配接時雙向的,互通的,不需要各自和各自的節點手動連接配接,隻要節點之間有其他節點相連都可以自動相連。現在各個節點之間都可以自由通信了,不過,現在的叢集還沒有搭建好,還沒有配置設定槽點。

redis筆記⑤——redis叢集
  1. 指派槽點

通過下面的節點指派槽點,redis總共有16384個槽點,這些槽點決定了那些資料存在哪些節點中,我們總共有兩個主機節點,是以平分就是每個主機節點8192個槽點。這裡如果手動配置設定那會很慢,可以通過腳本配置設定。

redis筆記⑤——redis叢集

腳本如下

redis筆記⑤——redis叢集

這裡将槽點平分給7000和7001這兩個節點

redis筆記⑤——redis叢集
  1. 配置設定主從

上面我們看到隻存在主機節點,沒有從節點,是以我們需要配置設定主從,我們需要通過從機的用戶端指定它的主機節點,這裡指定的是節點的id,就是nodes資訊裡最前面的一串字元。

redis筆記⑤——redis叢集
redis筆記⑤——redis叢集

現在我們發現主從已經配置設定好了

redis筆記⑤——redis叢集
  1. 打開用戶端

現在我們可以通過叢集的方式打開用戶端,隻需要在最後加上

-c

就可以了

redis筆記⑤——redis叢集

現在redis叢集就搭建好了

使用redis提供的腳本

這裡我們會搭建一個6個節點的叢集,3主3從,而且在5.0之後redis提供了另一種搭建的辦法,就不需要借助rb了,這裡也是主要介紹的也是這種方式。

首先配置還是和之前一摸一樣,就不再過多介紹了,我們直接從叢集的搭建開始介紹。

這裡有兩種方式:

  1. 5.0之前redis提供的rb腳本,不過這種方式需要首先擁有一個rb環境,安裝rb,然後通過rb腳本搭建叢集

    (1)

    cd /usr/local/redis3/src

    (2)

    ./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

  2. 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個節點的配置檔案,然後啟動每一個節點。

redis筆記⑤——redis叢集

隻需要執行下面的指令就可以直接搭建叢集(5.0以後),我們可以在幫助中檢視這個指令的各種參數的說明。

redis筆記⑤——redis叢集

我們搭建叢集需要使用的指令就是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筆記⑤——redis叢集

這時叢集就已經搭建好了

redis筆記⑤——redis叢集

叢集伸縮

叢集擴容

對于已經搭建好的叢集我們可能還需要添加節點,這時我們就需要手動對這個叢集擴容,下面我會示範對一個叢集添加一個主節點和它的一個從節點。

同樣有兩種方式實作擴容,一種是原生的,一種是通過redis-cli的指令擴容。

  1. 原生的文法:

    cluster meet ip port

  2. redis-cli的指令:

    add-node 新節點ip 端口 已存在節點ip 端口

下面是示範的通過redis-cli的指令實作叢集擴容

首先還是需要其他需要添加的節點

redis筆記⑤——redis叢集

把節點加入叢集

redis筆記⑤——redis叢集

現在我們需要将從節點加入叢集并且配置設定主從,這裡可以采用先将從節點加入叢集再配置設定叢集,也可以通過redis-cli指令直接将從節點加入叢集并一起配置設定主從,配置設定主從前面已經介紹了原生的方法,這裡介紹redis-cli指令直接配置設定主從節點。

add-node 新節點ip  端口  已存在節點ip 端口  --cluster-slave --cluster-master-id masterID
           

遷移槽和資料

我們可以動态遷移槽和資料

  1. 首先開始槽遷移
/usr/local/bin/redis-cli --cluster reshard 192.168.123.19:8000
           
redis筆記⑤——redis叢集
  1. 指定遷移的計劃

首先是需要配置設定多少槽

redis筆記⑤——redis叢集

選擇接收的節點

redis筆記⑤——redis叢集

接着就是輸入槽點的源節點,我們配置設定的槽點就是由這些節點得來的,這裡我們可以輸入一個或多個源槽位的節點ID,輸入完了就輸入done表示結束,如果我們需要從所有其他的節點拿到槽位可以直接輸入all,這裡都是平均拿到槽位。

redis筆記⑤——redis叢集

叢集縮容

下線遷移槽

可以通過下面的指令将某個節點的一些槽位遷移給其他的節點

redis-cli --cluster reshard --cluster-from 要遷出節點ID  --cluster-to  接收槽節點ID --cluster-slots 遷出槽數量 已存在節點ip 端口
           
移除節點

可以通過下面的指令将節點移出叢集,注意這裡不僅會将節點移出叢集而且還會關閉這個redis節點

redis-cli --cluster del-node 已存在節點ip:端口 要删除的節點ID
           

故障轉移

redis叢集的故障轉移和哨兵模式下的故障轉移類似,下面是故障轉移的過程:

  1. 故障發現: 通過ping/pong消息實作故障發現(不依賴sentinel)
  2. 故障恢複:
    1. 檢查從節點的資格,每個從節點檢查與主節點的斷開時間,超過

      cluster-node-timeout

      *

      cluster-replica-validity-factor

      時間取消資格
    2. 選擇偏移量最大的,替換主節點
    3. 撤銷以前主節點的槽位,給新的主節點
    4. 向叢集廣播消息,表明已經替換了故障節點

叢集用戶端

redis筆記⑤——redis叢集

smart用戶端

  1. 從叢集中選取一個可運作節點,使用cluster slots初始化槽和節點映射。
  2. 将cluster slots的結果映射到本地,為每個節點建立jedispool
  3. 準備執行指令
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叢集用戶端可能出現的問題:

  1. moved重定向:指我們發送指令時,會對發送的key進行crc16算法,得到一個數字,然而我們連接配接的用戶端并不是管理這個數字的範圍,是以會傳回錯誤并告訴你此key應該對應的槽位,然後用戶端需要捕獲此異常,重新發起請求到對應的槽位。
  2. asx重定向:指在我們送發指令時,對應的用戶端正在遷移槽位中,是以此時我們不能确定這個key是還在舊的節點中還是新的節點中。
  3. 如果我們使用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);
        }
    }
}
           

繼續閱讀