Redis——Sentinel

weixin_34402408發表於2018-07-16
Sentinel——主從複製高可用方案

在使用主從複製時,我們面臨以下問題:

  • 手動故障轉移


    7432257-f2761cf6d98f6634.png
    master當機

    在發生上圖中的master當機之後,我們必須手動替換主節點,就像下圖:


    7432257-338c845d27ee4a89.png
    master當機處理
  • 寫能力和儲存能力受限(採用讀寫分離時)
    因此,Redis為我們提供了Sentinel架構:


    7432257-dd7291080ff7ddc6.png
    Sentinel.png

Sentinel是一個管理redis例項的工具,它可以實現對redis的監控、通知、自動故障轉移。sentinel不斷地檢測redis例項是否可以正常工作,通過API向其他程式報告redis的狀態,如果redis master不能工作,則會自動啟動故障轉移程式,將其中的一個slave提升為master,其他的slave重新設定新的master伺服器。

  • Sentinel作用:
    • Master狀態檢測
    • 如果Master異常,則會進行Master-Slave切換,將其中一個Slave作為Master,將之前的Master作為Slave
    • Master-Slave切換後,master_redis.conf、slave_redis.conf和sentinel.conf的內容都會發生改變,即master_redis.conf中會多一行slaveof的配置,sentinel.conf的監控目標會隨之調換
  • sentinel實現原理
    1. sentinel建立兩個連線master的 非同步網路連線

      • 命令連線:向master傳送命令,並接受回覆(預設十秒一次通過INFO來獲取master資訊)
      • 訂閱連線:訂閱master的sentinel:hello頻道(依賴於Redis的訂閱釋出,因為我們以前說過Redis是不能儲存過期釋出訊息的,所以,slave節點可能錯過master的訊息,所以需要sentinel來接受這些資訊提供給slave,同時其他sentinel也可以獲取最新資訊
    2. sentinel建立兩個連線slave的 非同步網路連線(當發現新的slave出現時)

      • 命令連線:向slave傳送命令,並接受回覆(預設十秒一次通過INFO來獲取slave資訊)
      • 訂閱連線:訂閱slave的頻道
    3. 建立連向其他sentinel的命令連線(用於資訊交換:主觀/客觀下線檢測)

      • sentinels字典:為master建立,用於儲存自身以及其他監視該master的sentinel節點資訊
        不需要訂閱連線的原因:sentinel訂閱了master和slave的頻道,會接受到新sentinel的資訊
    4. 下線檢測:

      • sentinel預設每秒一次向建立了命令連線的所有節點(包括sentinel)傳送PING命令,用於檢測是否線上
      • 主觀下線:當master超過down-after-milliseconds設定的時間仍然返回無效回覆時,這時候sentinel將master標記為主觀下線
      • 客觀下線:在標記為主觀下線後,改sentinel會想其他監視該master的節點通過命令連線進行詢問,當贊同的數量達到我們設定的數量時,認為該master客觀下線。
        上面的總結一下可以認為是sentinel的三個定時任務:
      1. 每10秒每個sentinel對master和slave執行INFO(故障轉移時改為1秒)
      • 發現slave節點
      • 確認主從關係
      1. 每2秒每個sentinel通過節點的channel向所有主從節點傳送命令
      • 交換對master節點的看法和自身資訊(更新sentinels字典以及master節點例項結構)
      1. 每1秒每個sentinel對其他的sentinel和節點執行ping(心跳檢測)
      • 下線檢測(主觀下線判斷)
  1. 故障轉移
    在判斷master客觀下線後,選舉出的sentinel將進行故障轉移
    1. 多個sentinel發現並確認master有問題
    2. 選舉出一個sentinel作為領導
    3. 選舉出一個slave作為master
    4. 通知其餘slave成為新的master的slave
    5. 通知客戶端主從變化
    6. 等待老的master復活成為新master的slave
    • 領導者選舉
      • 每個做主觀下線的sentinel節點向其他sentinel節點傳送sentinel is-master-down-by-addr 命令,要求成為領導者
      • 收到命令的sentinel節點如果沒有同意過其他sentinel節點的請求,將同意該請求,否則拒絕
      • 當票數超過sentinel節點半數且超過quorum,將成為領導者
      • 若此過程有多個sentinel節點成為領導者,將等待一會重新選舉
  • sentinel備份策略
    可以參考以前的文章《Redis——持久化》
  • 部署sentinel主從配置
    我們這裡是採用了三臺物理機,一共五個節點,一主四從,三個sentinel節點。
    • 主節點主要配置
#開放外網訪問
bind 0.0.0.0 
#下面我們設定了密碼,所以開啟安全模式
protected-mode yes
#開放埠
port 6379
#設定為守護執行緒
daemonize yes
#pid檔案
pidfile "/var/run/redis_6379.pid"
#日誌檔案
logfile "6379.log"
#rdb檔案
dbfilename "dump-6379.rdb"
#檔案路徑
dir "/var/redis/6379"
#主節點訪問密碼(主節點也要設定是因為故障切換後主節點有可能變為從節點)
masterauth "password"
requirepass "password"
  • 從節點配置
#開放外網訪問
bind 0.0.0.0 
#下面我們設定了密碼,所以開啟安全模式
protected-mode yes
#開放埠
port 6380
#設定為守護執行緒
daemonize yes
#pid檔案
pidfile "/var/run/redis_6380.pid"
#日誌檔案
logfile "6379.log"
#rdb檔案
dbfilename "dump-6380.rdb"
#檔案路徑
dir "/var/redis/6380"
#主節點訪問密碼
masterauth "password"
#該節點密碼
requirepass "password"
#設定主節點 我用的公網ip
slaveof 120.1.1.1 6379
#設定從節點只讀
slave-read-only yes

其他從節點類似

  • 哨兵節點配置
bind 0.0.0.0
port 26379
#要監控的主節點名稱、ip、port ,2表示將這個主伺服器判斷為失效至少需要 2 個 Sentinel 同意 
sentinel monitor mymaster 120.1.11.11 6379 2
# Sentinel 認為伺服器已經斷線所需的毫秒數。
sentinel down-after-milliseconds mymaster 60000
# failover過期時間,當failover(選出新的master)開始後,在此時間內仍然沒有觸發任何failover操作,當前sentinel  將會認為此次failoer失敗
sentinel failover-timeout mymaster 180000
#指定了在執行故障轉移時, 最多可以有多少個從伺服器同時對新的主伺服器進行同步, 這個數字越小, 完成故障轉移所需的時間就越長。
sentinel parallel-syncs mymaster 1

其他哨兵節點類似,只是埠不同。

  • 哨兵模式測試
    編寫一個工具類,用來獲取jedis例項
public class RedisUtil {

  /*redis-sentinel節點地址*/
  private static final String SENTINEL_1 = "ip:26379";
  private static final String SENTINEL_2 = "ip1.31:26380";
  private static final String SENTINEL_3 = "ip:26381";
  //sentinel節點地址集合
  private static final Set<String> SENTINELS = new HashSet<String>() {
    {
      add(SENTINEL_1);
      add(SENTINEL_2);
      add(SENTINEL_3);
    }
  };

  //masterName
  private static final String MASTER_NAME = "mymaster";
  //  訪問密碼
  private static String AUTH = "password";
  private static String HOST = "ip";
  /**
   * 可用連線例項的最大數目,預設值為8; 如果賦值為-1,則表示不限制;如果pool已經分配了maxActive個jedis例項,則此時pool的狀態為exhausted(耗盡)。
   */
  private static int MAX_ACTIVE = 1024;
  // 控制一個pool最多有多少個狀態為idle(空閒的)的jedis例項,預設值也是8。
  private static int MAX_IDLE = 200;
  // 等待可用連線的最大時間,單位毫秒,預設值為-1,表示永不超時。如果超過等待時間,則直接丟擲JedisConnectionException;
  private static int MAX_WAIT = 10000;
  private static int TIMEOUT = 10000;
  // 在borrow一個jedis例項時,是否提前進行validate操作;如果為true,則得到的jedis例項均是可用的;
  private static boolean TEST_ON_BORROW = true;
  /*sentinelPool*/
  private static JedisSentinelPool SENTINEL_POLL = null;

  /**
   * 初始化Redis-Sentinel連線池.
   */
  static {
    try {
      // maxActive ==> maxTotal
      // maxWait ==> maxWaitMillis
      JedisPoolConfig CONFIG = new JedisPoolConfig();
      CONFIG.setMaxTotal(MAX_ACTIVE);
      CONFIG.setMaxIdle(MAX_IDLE);
      CONFIG.setMaxWaitMillis(MAX_WAIT);
      CONFIG.setTestOnBorrow(TEST_ON_BORROW);
      SENTINEL_POLL = new JedisSentinelPool(MASTER_NAME, SENTINELS, AUTH);
      System.out.println(SENTINEL_POLL.getCurrentHostMaster());
    } catch (Exception e) {
      e.printStackTrace();
    }
  }


  /**
   * 獲取Jedis例項.
   */
  public static synchronized Jedis getJedis() {
    Jedis jedis = null;
    int count = 0;
    do {
      try {
        jedis = SENTINEL_POLL.getResource();
      } catch (Exception e) {
        e.printStackTrace();
      }
      count++;
    } while (jedis == null && count < 10);
    return jedis;
  }
  /**
   * 釋放jedis資源
   */
  public static void returnResource(final Jedis jedis) {
    if (jedis != null) {
      jedis.close();
    }
  }
}

測試客戶端

public class RedisClient {

  public static void main(String[] args) {
    Jedis jedis = RedisUtil.getJedis();
    jedis.set("4", "1");
    jedis.set("5", "2");
    jedis.set("6", "3");
    System.out.println(jedis.get("4"));
    System.out.println(jedis.get("5"));
    System.out.println(jedis.get("6"));
    RedisUtil.returnResource(jedis);

  }
}

7432257-e7b2959aca0c66ce.png
測試結果

可以看到,可以成功使用了,我們再來測試一下故障轉移是否可用
我們手動關閉主節點,可以看到主節點已經關閉
7432257-9151060d1738495e.png
圖片.png

再來執行一下程式,
7432257-2dd16e67a6fb1259.png
故障轉移

發現主節點已經自動切換為其他的節點,最後我們再啟動剛才關閉的主節點,發現他已經成為新主節點的從節點
7432257-0bab5a89db9a63c2.png
圖片.png

相關文章