zoukankan      html  css  js  c++  java
  • Redis Sentinel主从高可用方案

    Redis Sentinel主从高可用方案

    本文介绍一种通过Jed和Sentinel实现Redis集群(主从)的高可用方案,该方案需要使用Jedis2.2.2及以上版本(强制),Redis2.8及以上版本(可选,Sentinel最早出现在Redis2.4中,Redis2.8中Sentinel更加稳定),

    附:
    Redis Cluster集群主从方案:http://www.cnblogs.com/soul-wonder/p/8891256.html
    Redis Sentinel主从高可用方案:http://www.cnblogs.com/soul-wonder/p/8891217.html

    一、Sentinel介绍

    Sentinel是Redis的高可用性(HA)解决方案,由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进行下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。Redis提供的sentinel(哨兵)机制,通过sentinel模式启动redis后,自动监控master/slave的运行状态,基本原理是:心跳机制+投票裁决

    • 监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
    • 提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
    • 自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。

    二、Sentinel的主从原理

       

    之前介绍过为什么Jedis要用2.2.2及以上版本,因为主从实例地址(IP PORT)是不同的,当故障发生进行主从切换后,应用程序无法知道新地址,故在Jedis2.2.2中新增了对Sentinel的支持,应用通过redis.clients.jedis.JedisSentinelPool.getResource()取得的Jedis实例会及时更新到新的主实例地址。

    三、Redis Sentinel配置

    这里我采用2个哨兵,1个主redis,2个从redis的方式,配置文件如下:

    sentinel_63791.conf 配置:

    port 63791
    daemonize yes
    logfile "/var/log/sentinel_63791.log"
    #master-1
    sentinel monitor master-1 192.168.78.99 6379 2
    sentinel down-after-milliseconds master-1 5000
    sentinel failover-timeout master-1 18000
    sentinel auth-pass master-1 yingjun
    sentinel parallel-syncs master-1 1
    

    sentinel_63792.conf 配置:

    port 63792
    daemonize yes
    logfile "/var/log/sentinel_63792.log"
    #master-1
    sentinel monitor master-1 192.168.78.99 6379 2
    sentinel down-after-milliseconds master-1 5000
    sentinel failover-timeout master-1 18000
    sentinel auth-pass master-1 yingjun
    sentinel parallel-syncs master-1 1
    

    redis_master_6379.conf 配置:
    在原配置文件中作如下修改:

    port 6379
    daemonize yes
    requirepass yingjun
    masterauth yingjun
    

    redis_slave_6380.conf 配置:
    在原配置文件中作如下修改:

    port 6380
    daemonize yes
    requirepass yingjun
    slaveof 192.168.78.99 6379
    masterauth yingjun
    

    redis_slave_6381.conf 配置:
    在原配置文件中作如下修改:

    port 6381
    daemonize yes
    requirepass yingjun
    slaveof 192.168.78.99 6379
    masterauth yingjun
    

    按如下顺序依次启动服务:

    ./redis-server ../conf/redis_master_6379.conf
    ./redis-server ../conf/redis_slave_6381.conf    
    ./redis-server ../conf/redis_slave_6382.conf    
    ./redis-sentinel ../conf/sentinel_63791.conf
    ./redis-sentinel ../conf/sentinel_63792.conf
    

    查看进程是否都已经启动:

    查看master的状态:

    查看slave的状态:

    查看sentinel的状态:

    接下来验证redis sentinel的主从切换:

    • 首先关闭主redis(6379)服务(shutdown)。
    • 查看哨兵,发现端口号为6380的从服务变成了主服务,sentinel自动完成了故障切换。

    • 启动刚才被shutdown的6379服务并查看,发现它变成了从服务。

    三、Jedis Sentinel教程

    Maven依赖:

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.8.0</version>
        </dependency>
        <!-- spring-redis -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>1.6.4.RELEASE</version>
        </dependency>

    redis的配置文件:

    #redis config
    redis.pass=yingjun
    redis.pool.maxTotal=105
    redis.pool.maxIdle=10
    redis.pool.maxWaitMillis=60000
    redis.pool.testOnBorrow=true
    
    sentinel1.ip=192.168.78.99
    sentinel1.port=63791
    
    sentinel2.ip=192.168.78.99
    sentinel2.port=63792
    
    

    Spring的配置文件:

    <!-- Redis 配置 -->
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxTotal" value="${redis.pool.maxTotal}" />
        <property name="maxIdle" value="${redis.pool.maxIdle}" />
        <property name="maxWaitMillis" value="${redis.pool.maxWaitMillis}" />
        <property name="testOnBorrow" value="${redis.pool.testOnBorrow}" />
    </bean>
    
    <bean id="sentinelConfiguration"
        class="org.springframework.data.redis.connection.RedisSentinelConfiguration">
        <property name="master">
            <bean class="org.springframework.data.redis.connection.RedisNode">
                <property name="name" value="master-1"></property>
            </bean>
        </property>
        <property name="sentinels">
            <set>
                <bean class="org.springframework.data.redis.connection.RedisNode">
                    <constructor-arg name="host" value="${sentinel1.ip}"></constructor-arg>
                    <constructor-arg name="port" value="${sentinel1.port}"></constructor-arg>
                </bean>
                <bean class="org.springframework.data.redis.connection.RedisNode">
                    <constructor-arg name="host" value="${sentinel2.ip}"></constructor-arg>
                    <constructor-arg name="port" value="${sentinel2.port}"></constructor-arg>
                </bean>
            </set>
        </property>
    </bean>
    <!-- Jedis ConnectionFactory连接配置 -->
    <bean id="jedisConnectionFactory"
        class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="password" value="${redis.pass}"></property>
        <property name="poolConfig" >
            <ref bean="jedisPoolConfig"/>
        </property>
        <constructor-arg name="sentinelConfig" ref="sentinelConfiguration"></constructor-arg>
    </bean>
    
    <!-- redisTemplate配置,redisTemplate是对Jedis的对redis操作的扩展,有更多的操作,封装使操作更便捷 -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFactory" />
    </bean>

    代码中直接用redisTemplate调用:

        @Override
        public boolean add(final KeyToken tkey) {
            boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
    
                @Override
                public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                    RedisSerializer<String> serializer = getRedisSerializer();
                    byte[] key = serializer.serialize(tkey.getIndex());
                    byte[] name = serializer.serialize(tkey.getExpire_time());
                    return connection.setNX(key, name);
                }
    
            });
            return result;
        }

    JedisPool:

      private static JedisSentinelPool pool = null; 
      private static Set<String> sentinels;
      private static String redisPassword = "123456";
    
      /** 
       * 创建连接池 
       * 
       */
      private static void createJedisPool() { 
          String redisSentinels = EposUtil.getRedishosts();
            sentinels = new HashSet<String>();
            sentinels.add(redisSentinels.split(",")[0]);
            sentinels.add(redisSentinels.split(",")[1]);
            sentinels.add(redisSentinels.split(",")[2]);
            pool =  new JedisSentinelPool("mymaster", sentinels);
      } 
    
      /** 
       * 在多线程环境同步初始化 
       */
      private static synchronized void poolInit() { 
        if (pool == null) {
            createJedisPool(); 
        }
      } 
    
      /** 
       * 获取一个jedis 对象 
       * 
       * @return 
       */
      public static Jedis getJedis() { 
        if (pool == null) 
          poolInit(); 
        return pool.getResource(); 
      } 
    
    /**
         * 向redis中存储值,不带失效时间
         * 
         * @param key
         * @param value
         */
        public static void setValue(String key, String value) {
            Jedis jedis = getJedis();
    
            try {
                jedis.auth(redisPassword);
                jedis.set(key, value);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (jedis != null) {
                    jedis.close();
                }
            }
        }
    
        /**
         * 向redis中存储值,带失效时间
         * 
         * @param key
         * @param value
         * @param seconds
         *            失效时间
         */
        public static void setValueAndExpires(String key, String value, int seconds) {
            Jedis jedis = null;
            try {
                jedis = getJedis();
                jedis.auth(redisPassword);
                jedis.set(key, value);
                jedis.expire(key, seconds);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (jedis != null) {
                    jedis.close();
                }
            }
        }
    
        /**
         * 获取值
         * 
         * @param key
         * @return
         */
        public static String getValue(String key) {
            Jedis jedis = null;
            String result = null;
    
            try {
                jedis = getJedis();
                jedis.auth(redisPassword);
                result = jedis.get(key);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (jedis != null) {
                    jedis.close();
                }
            }
    
            return result;
        }
    
        /**
         * 加锁
         * 
         * @param key
         */
        public static boolean lock(String key, int seconds) {
            Jedis jedis = null;
            boolean result =false;
            try {
                jedis =getJedis();
                jedis.auth(redisPassword);
                long res = jedis.setnx(key, "LOCKED");
                if(res==1L){
                    jedis.expire(key, seconds);
                    result= true;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (jedis != null) {
                    jedis.close();
                }
            }
            return result;
        }
    
        /**
         * 释放锁
         */
        public static void release(String key) {
            Jedis jedis = null;
    
            try {
                jedis = getJedis();
                jedis.auth(redisPassword);
                jedis.del(key);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (jedis != null) {
                    jedis.close();
                }
            }
        }
    View Code 
  • 相关阅读:
    [转载] $CF652B$ 题解
    [转载] $Luogu$ $P4951$ 题解
    luoguP3723 HNOI2017 礼物
    动态dp学习笔记
    noip级别模板小复习
    noip2017简要题解。
    noip考前抱佛脚 数论小总结
    HDU 6071 Lazy Running
    POI2012 ODL-Distance
    [NOI2002]荒岛野人
  • 原文地址:https://www.cnblogs.com/soul-wonder/p/8891217.html
Copyright © 2011-2022 走看看