Redis高可用之sentinel哨兵監控

上一篇文章介紹了Redis主從複製實現了redis高可用,但是主從複製存在着一些問題:

        1. 當master節點出現故障時,往往需要手動進行故障的轉移(這裏的手動也指寫腳本之類的)

        2. 當master節點出現故障時,就導致了另一個問題:寫能力和存儲能力受到限制

本次通過redis-sentinel哨兵實現故障的自動轉移

redis-sentinel故障轉移的流程:

1.當多個sentinel發現並確認了master有問題

2.接着會選舉出一個sentinel作爲領導

3.再選舉出一個slave作爲master

4.通知其餘的slave,新的master是誰

5.通知客戶端一個主從的變化

6.最後,sentinel會等待舊的master復活,然後將新master成爲slave

 

實操演練:

首先啓動三個redis節點(redis-7000, redis-7001, redis-7002),一主兩從模式,基本配置如下

主節點:redis-7000.conf

        port 7000

        daemonize yes

        pidfile /var/run/redis-7000.pid

        logfile "7000.log"

        dir "/opt/redis/data"

從節點:redis-7001.conf     redis-7002.conf

        port 7001(redis-7002.conf是7002)

        daemonize yes

        pidfile /var/run/redis-7000.pid (redis-7002.conf是7002 )

        logfile "7000.log" (redis-7002.conf是7002 )

        dir "/opt/redis/data"

        slaveof 127.0.0.1 7000

驗證:進入任意一個redis節點,執行info replication命令即可查看當前節點的狀態

可以看出端口7000的節點時master節點

可以看出當前節點時slave從節點,並且可以看出該從節點的主節點地址

 

其次啓動三個哨兵(redis-26379, redis-26380, redis-26381)

配置(三個配置基本相似, 就端口號的地方改一下)

port 26379
daemonize yes
dir "/opt/redis/data/sentinel"
logfile "26379.log"

sentinel monitor mymaster 127.0.0.1 7000 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
#sentinel auth-pass mymaster luyue

配置解釋:

sentinel monitor mymaster 127.0.0.1 7000 2 : mymaster(監控的主節點的名字) 127.0.0.1(監控的主節點的ip) 7000(監控的主節點的端口號)  2(故障發現,至少要多少個sentinel發現主節點是故障的才進行轉移)

sentinel down-after-milliseconds mymaster 30000: 對master進行判斷,類似不斷ping,若30秒後節點依然不通,則認爲是故障的

sentinel parallel-syncs mymaster 1 : slave對新的master進行復制,若舊master故障, 1代表每次只能多少個slave對master進行復制

sentinel failover-timeout mymaster 180000 : 故障轉移時間

sentinel auth-pass mymaster : 若master節點配置了masterauth,則需要,作用就是安全認證

!!!:redis-sentinel可以監控多套主從配置,只要將mymaster 名字變掉就行

驗證:連接sentinel,隨便一個

輸入info命令,可以看到sentinels=3, 則證明sentinel啓動成功

 

ps:接下來的主題是java如何連接redis-sentinel,與redis-sentinel基本無關,可跳過

通過jedis連接redis-sentinel

POM:

<!--jedis-->
<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
  <version>2.6.0</version>
</dependency>

一:封裝SentinelJedisPool

package com.mmall.common;

import com.google.common.collect.Sets;
import com.mmall.util.PropertiesUtil;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisSentinelPool;

import java.util.Set;

/**
 * @author Luyue
 * @date 2018/8/11 14:29
 **/
public class SentinelJedisPool {
    private static JedisSentinelPool pool;

    //最大連接數
    private static Integer maxTotal = Integer.parseInt(PropertiesUtil.getKey("redis.max.total", "20"));
    //最大空閒連接數
    private static Integer maxIdle = Integer.parseInt(PropertiesUtil.getKey("redis.max.idle", "10"));
    //最小空閒連接數
    private static Integer minIdle = Integer.parseInt(PropertiesUtil.getKey("redis.min.idle", "2"));

    //通過連接池拿去jedis連接時,校驗並返回可用連接
    private static Boolean testOnBorrow = Boolean.parseBoolean(PropertiesUtil.getKey("redis.test.borrow", "true"));
    //通過連接池返還jedis連接時,校驗該連接
    private static Boolean testOnReturn = Boolean.parseBoolean(PropertiesUtil.getKey("redis.test.return", "true"));

    /**
     * 初始化連接池
     */
    private static void initPool() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();

        jedisPoolConfig.setMaxTotal(maxTotal);
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMinIdle(minIdle);
        jedisPoolConfig.setTestOnBorrow(testOnBorrow);
        jedisPoolConfig.setTestOnReturn(testOnReturn);

        //當連接池無空閒連接時,是否阻塞
        jedisPoolConfig.setBlockWhenExhausted(true);

        //sentinel節點的地址,格式:ip:port
        Set<String> sentinels = Sets.newHashSet("xxx.xxx.xxx.xxx:26379", "xxx.xxx.xxx.xxx:26380", "xxx.xxx.xxx.xxx:26381");

        pool = new JedisSentinelPool("mymaster", sentinels, jedisPoolConfig, 2 * 1000, "luyue");
    }

    static {
        initPool();
    }

    /**
     * 獲取一個連接
     * @return
     */
    public static Jedis getJedis() {
        return pool.getResource();
    }

    /**
     * 返還錯誤的連接
     * @param jedis
     */
    public static void returnBrokenJedis(Jedis jedis) {
        pool.returnBrokenResource(jedis);
    }

    /**
     * 返還連接
     * @param jedis
     */
    public static void returnJedis(Jedis jedis) {
        pool.returnResource(jedis);
    }
}

二. 封裝API

package com.mmall.util;

import com.mmall.common.SentinelJedisPool;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;

/** 單機redis連接池
 * @author Luyue
 * @date 2018/8/11 14:29
 **/
@Slf4j
public class SentinelJedisPoolUtil {

    /**
     * 存儲鍵值
     * @param key
     * @param value
     * @return
     */
    public static String set(String key, String value) {
        Jedis jedis = null;
        String response = null;

        try {
            jedis = SentinelJedisPool.getJedis();
            response = jedis.set(key, value);
        } catch (Exception e) {
            log.error("set key:{}, value:{} is error:{}", key, value, e);
            SentinelJedisPool.returnBrokenJedis(jedis);
            return response;
        }

        SentinelJedisPool.returnJedis(jedis);
        return response;
    }

    /**
     * 存儲鍵值,並設置有效時間
     * @param key
     * @param value
     * @param exTime
     * @return
     */
    public static String setEx(String key, String value, int exTime) {
        Jedis jedis = null;
        String response = null;

        try {
            jedis = SentinelJedisPool.getJedis();
            response = jedis.setex(key, exTime, value);
        } catch (Exception e) {
            log.error("setEx key:{}, value:{} is error:{}", key, value, e);
            SentinelJedisPool.returnBrokenJedis(jedis);
            return response;
        }

        SentinelJedisPool.returnJedis(jedis);
        return response;
    }

    /**
     * 獲取value
     * @param key
     * @return
     */
    public static String get(String key) {
        Jedis jedis = null;
        String response = null;

        try {
            jedis = SentinelJedisPool.getJedis();
            response = jedis.get(key);
        } catch (Exception e) {
            log.error("get key:{}, is error:{}", key, e);
            SentinelJedisPool.returnBrokenJedis(jedis);
            return response;
        }

        SentinelJedisPool.returnJedis(jedis);
        return response;
    }

    /**
     * 設置鍵的有效時間
     * @param key
     * @param exTime
     * @return
     */
    public static Long expire(String key, int exTime) {
        Jedis jedis = null;
        Long response = null;

        try {
            jedis = SentinelJedisPool.getJedis();
            response = jedis.expire(key, exTime);
        } catch (Exception e) {
            log.error("expire key:{}, is error:{}", key, e);
            SentinelJedisPool.returnBrokenJedis(jedis);
            return response;
        }

        SentinelJedisPool.returnJedis(jedis);
        return response;
    }

    /**
     * 刪除鍵
     * @param key
     * @return
     */
    public static Long del(String key) {
        Jedis jedis = null;
        Long response = null;

        try {
            jedis = SentinelJedisPool.getJedis();
            response = jedis.del(key);
        } catch (Exception e) {
            log.error("del key:{}, is error:{}", key, e);
            SentinelJedisPool.returnBrokenJedis(jedis);
            return response;
        }

        SentinelJedisPool.returnJedis(jedis);
        return response;
    }
}

三. 開始測試

在測試之前,有一個點,非常非常重要,因爲我們上述配置沒有設置密碼,所以redis會默認採取保護模式,這就導致了客戶端沒有權限獲取連接,報DENIED Redis is running in protected mode錯誤.

兩種辦法:

1. 設置密碼,requirepass和masterauth

2. 關閉保護模式: protected-mode no(redis-7000.conf和sentinel.conf都加上)

一般我兩種辦法都加上,一種可能也會報錯, 在然後將bind 127.0.0.1 配置給註釋掉, 最後sentinel配置文件中的127.0.0.1必須填主機的線上地址

redis-7000.conf: 

redis-sentinel-26379.conf(其他兩個哨兵配置除了端口,其他一樣)

ok,所有redis節點,sentinel節點開啓

接着寫java測試代碼:

運行結果:

切換到debug模式,查看jedis是否真的獲取到了

完全ok,結束!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章