1. 程式人生 > >7、redis主從複製和sentinel配置高可用

7、redis主從複製和sentinel配置高可用

開發十年,就只剩下這套架構體系了! >>>   

一:redis主從配置

  • 1、環境準備

      master :  192.168.50.10  6179
      slave1:    192.168.50.10  6279
      slave2:    192.168.50.10  6379
    
  • 2、redis.conf配置檔案配置

    • master

        port 6179
        requirepass 123456				#密碼認證,可以不設定
        dir "/var/redis/6179"                         #工作目錄,dump.rdb會保留在這個目錄
      
    • slave1

        port 6279
        slaveof 192.168.50.10 6179	 	#master的ip和埠,不能使用127.0.0.1 6179
        masterauth 123456                         #如果master設定了requirepass,則這裡需要配置
        slave-read-only yes				#預設就是yes
        dir "/var/redis/6279"
      
    • slave2

        port 6379
        slaveof 192.168.50.10 6179	 	#master的ip和埠,不能使用127.0.0.1 6179
        masterauth 123456                         #如果master設定了requirepass,則這裡需要配置
        slave-read-only yes				#預設就是yes
        dir "/var/redis/6379"
      

    注意:如果slaveof 127.0.0.1 6179,則連結sentinel時提示是127.0.0.1,無法連線

  • 3、啟動redis並測試 ==> 如果設定了daemonize yes則沒有日誌輸出,預設是no

    • 啟動: nohup redis-server /redis/redis.conf $1>>/var/log/redis/redis.log 2>&1 &

      master 127.0.0.1:6179>info replication

      slave1 127.0.0.1:6279>info replication

      slave2 127.0.0.1:6379>info replication

    • 在master中執行如下語句,如果所有機器執行keys* 都能查出name這個key說明能夠複製

         127.0.0.1:6179> set name nametest
      
  • 4、日誌分析

    • slave1和slave2一啟動就會去連結master

    • master啟動後,接受slave1和slave2的資訊同步請求,進行RDB持久化(在記憶體中持久化)

  • 5、redis主從同步規則

      Salve會發送sync命令到Master
      Master啟動一個後臺程序,將Redis中的資料快照儲存到檔案中
      啟動後臺程序的同時,Master會將儲存資料快照期間接收到的寫命令快取起來
      Master完成寫檔案操作後,將該檔案傳送給Salve
      Salve將檔案儲存到磁碟上,然後載入檔案到記憶體恢復資料快照到Salve的Redis上
      當Salve完成資料快照的恢復後,Master將這期間收集的寫命令傳送給Salve端
      後續Master收集到的寫命令都會通過之前建立的連線,增量傳送給salve端
    

    總結一下,主從剛剛連線的時候,進行全量同步;全同步結束後,進行增量同步。當然,如果有需要,slave 在任何時候都可以發起全量同步

    注意:在redis2.8開始,slave重新啟動,會發送psync到master進行部分同步,而不是sync全量同步

二:Sentinel 元件實現HA配置

  • 1、介紹

    • Redis-Sentinel是Redis官方推薦的高可用性(HA)解決方案,功能類似於zk,用於監控redis master狀態,進行切換。

    • sentinel是和redis獨立的元件,不需要每個redis節點都啟動sentinel,可以在任何機器上部署,只要能夠連線redis master即可。

    • 其功能類似於zk,用於監聽和選舉,可以啟動多個sentinel作為叢集,只要連線的master相同,則認為是統一叢集,sentinel叢集中各個sentinel也有互相通訊,通過gossip協議

    • 一個sentinel可以監聽多個redis叢集,他們是通過連線到redis master,利用釋出/訂閱功能來發現其他sentinel的存在。

  • 2、配置引數介紹

    • 叢集1 mymaster

        # 至少2個sentinel認為mymaster掛了,才真正認為是掛了,所以sentinel至少要有2個在執行
        sentinel monitor mymaster 192.168.50.10 6179 2   
        # sentinel向master傳送ping,如果在這時間內沒有響應,則這個sentinel認為master掛了
        sentinel down-after-milliseconds mymaster 60000
        # 
        sentinel failover-timeout mymaster 180000
        sentinel parallel-syncs mymaster 1
      
    • 叢集2 resque

        sentinel monitor resque 192.168.50.10 6179 4
       	sentinel down-after-milliseconds resque 10000
       	sentinel failover-timeout resque 180000
       	sentinel parallel-syncs resque 5
      
  • 3、工作原理

    • 連結master,獲取slave和其他sentinel資訊,記錄到自己的sentinel.conf中,如下圖

    • sentinel發現down-after-milliseconds 時間內master沒有回覆,則認為failor,它會問叢集其他sentinel是否也有人認為失敗,個數達到sentinel monitor mymaster xxx 2,才會觸發failover

    • sentinel叢集選舉其中一個sentinel拿著一個唯一的版本號去進行master切換。此sentinel指定一個slave傳送SLAVE OF NO ONE,將其轉化為master,同時將這些配置資訊廣播給其他sentinel,進行更新master資訊

  • 4、啟動 ===> 如果設定了daemonize yes則沒有日誌輸出

      nohup redis-server redis-slave1/sentinel.conf --sentinel $1>>/var/log/sentinel/sentinel.log 2>&1 &
    
  • 5、失敗測試=> 6379變成了6179的redis

  • 6、注意

      當一個master配置為需要密碼才能連線時,客戶端和slave在連線時都需要提供密碼。
    
      master通過requirepass設定自身的密碼,不提供密碼無法連線到這個master。
      slave通過masterauth來設定訪問master時的密碼。
    
      但是當使用了sentinel時,由於一個master可能會變成一個slave,一個slave也可能會變成master,所以需要同時設定上述兩個配置項。
    

三:動態切換master測試

  • 1、pom依賴

      <dependency>
          <groupId>redis.clients</groupId>
          <artifactId>jedis</artifactId>
          <version>2.9.0</version>
      </dependency>
    
  • 2、程式碼

      public class JedisSentinelTest {
          public static void main(String[] args) {
              /**
               * 獲取master連結~方法一
               */
              HashSet<String> sentinelSet = new HashSet<>();
              sentinelSet.add("192.168.50.10:26179"); // 這個節點目前是掛掉的,也可以配置,後續啟動有效
              sentinelSet.add("192.168.50.10:26279");
              sentinelSet.add("192.168.50.10:26379");
              JedisSentinelPool myMasterSentinel = new JedisSentinelPool("mymaster", sentinelSet);
    
              Jedis master = myMasterSentinel.getResource();
              master.set("aaaa", "aaaTest");
              master.close();
    
              /**
               * 獲取master連結~方法二
               */
              Jedis sentinelNode = new Jedis("192.168.50.10", 26179);
              List<Map<String, String>> masterList = sentinelNode.sentinelMasters();
              Map<String, String> masterMap = null;
              for (Map<String, String> temp : masterList){
                  if ("mymaster".equals(temp.get("name"))){
                      masterMap = temp;
                      break;
                  }
              }
              System.out.println("masterIp:" + masterMap.get("ip"));
              System.out.println("masterPort:" + masterMap.get("port"));
    
              List<String> mymaster = sentinelNode.sentinelGetMasterAddrByName("mymaster");
              System.out.println("masterIp:" + mymaster.get(0));
              System.out.println("masterPort:" + mymaster.get(1));
    
              /**
               * 獲取slaves
               */
              Jedis jedis = new Jedis("192.168.50.10", 26179);
              List<Map<String, String>> slaves = jedis.sentinelSlaves("mymaster");
              for (Map<String, String> temp : slaves){
                  System.out.println("slaveIp:" + temp.get("ip"));
                  System.out.println("slaveIp:" + temp.get("port"));
              }
    
              /**
               * 主從切換
               */
              Map<String, String> first = slaves.get(0);
              Jedis toBeMaster = new Jedis(first.get("ip"), Integer.parseInt(first.get("port")));
              String flag = toBeMaster.slaveofNoOne();  // 將自己設定為主節點
    
              for (int i = 1; i < slaves.size(); i++){
                  Map<String, String> temp = slaves.get(i);
                  Jedis ortherSlave = new Jedis(temp.get("ip"), Integer.parseInt(temp.get("port")));
                  ortherSlave.slaveof(first.get("ip"), Integer.parseInt(first.get("port")));
              }
    
              Jedis slave = new Jedis("192.168.50.10", 6279);
              /**
               * 重新載入配置資訊,復原
               */
              slave.configResetStat();
    
              /**
               * 手動觸發RDB(save、bgsave)以及aof rewrite
               */
              slave.save();
              slave.bgsave();
              slave.bgrewriteaof();
    
              /**
               * 獲取資訊,可以知道自己是什麼角色
               */
              String info = slave.info();
              String replication = slave.info("replication");
          }
      }