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,结束!

 

展开阅读全文

没有更多推荐了,返回首页