12、Spring技术栈-Redis Sentinel实现高可用缓存集群方案实战

Redis Sentinel的分布式特性介绍

Redis Sentinel是一个分布式系统,Sentinel运行在有许多Sentinel进程互相合作的环境下,它本身就是这样被设计的。有许多Sentinel进程互相合作的优点如下:

  1. 当多个Sentinel同意一个master不再可用的时候,就执行故障检测。这明显降低了错误概率。
  2. 即使并非全部的Sentinel都在工作,Sentinel也可以正常工作,这种特性,让系统非常的健康。

所有的Sentinels,Redis实例,连接到Sentinel和Redis的客户端,本身就是一个有着特殊性质的大型分布式系统。在这篇文章中,我将通过实例的形式从部署Redis Sentinel集群、使用Redis的Master Slave的模式部署Redis集群来介绍在Spring项目中如何使用Redis Sentinel实现缓存系统的高可用。

部署之前了解关于Sentinel的基本东西

  1. 一个健康的集群部署,至少需要三个Sentinel实例
  2. 三个Sentinel实例应该被放在失败独立的电脑上或虚拟机中,比如说不同的物理机或者在不同的可用区域上执行的虚拟机。
  3. Sentinel + Redis 分布式系统在失败期间并不确保写入请求被保存,因为Redis使用异步复制。可是有很多部署Sentinel的 方式来让窗口把丢失写入限制在特定的时刻,当然也有另外的不安全的方式来部署。
  4. 客户端必须支持Sentinel。大多数客户端库都支持Sentinel,但并不是全部。
  5. 没有高可用的设置是安全的,如果你在你的测试环境没有经常去测试,或者甚至在生产环境中你也没有经常去测试,如果Sentinel正常工作。 但是你或许有一个错误的配置而仅仅只是在很晚的时候才出现(凌晨3点你的主节点宕掉了)。
  6. Sentinel,Docker ,其他的网络地址转换表,端口映射应该很小心的使用:Docker执行端口重新映射,破坏Sentinel自动发现另外的Sentinel进程和一个主节点的从节点列表。在文章的稍后部分查看更过关于Sentinel和Docker的信息。

实例介绍

  1. 本实例通过部署3个Redis Sentinel实现Sentinel实例的高可用。
  2. 本实例通过部署1个Redis Master实例,3个Slave实例实现Redis缓存的高可用。

Redis Master实例和Slave实例部署

下载并安装Redis,此文不讲解,具体可参考http://blog.csdn.net/zyhlwzy/article/details/78366265

可参考Redis主从(Master-Slave)复制(Replication)设置一文第8节Redis主从复制实战的部署方式部署Master-Slave,但是需要注意的是,本文需要的是一个Master实例,3个Slave实例。

Redis实例信息如下:

IPPort备注
192.168.199.1266379Master
192.168.199.2496382Slave
192.168.199.2496383Slave
192.168.199.2496384Slave

配置Master
下载并安装Redis,此文不讲解,具体可参考http://blog.csdn.net/zyhlwzy/article/details/78366265

Master的配置很简单,我们开启守护进程即可(演示实例不设置验证信息,如有需要自行设置)。

进入redis配置文件目录(/data/redis/redis-4.0.1/redis.conf)编辑redis.conf进行如下配置。

daemonize yes

配置Slave

下载并安装Redis,此文不讲解,具体可参考http://blog.csdn.net/zyhlwzy/article/details/78366265

创建Slave配置文件目录,拷贝redis.conf到对应目录并进行配置。

mkdir /data/redis/cluster
cd /data/redis/cluster
mkdir -p 6382
cp /data/redis/redis-4.0.1/redis.conf /data/redis/cluster/6382/redis-6382.conf
mkdir -p 6383
cp /data/redis/redis-4.0.1/redis.conf /data/redis/cluster/6383/redis-6383.conf
mkdir -p 6384
cp /data/redis/redis-4.0.1/redis.conf /data/redis/cluster/6384/redis-6384.conf

3个Slave实例的配置文件内容,注意修改下面加粗字体部分的内容即可(注意注释掉的要去掉注释),其他都相同:

配置选项选项值说明
daemonizeyes默认情况下 redis 不是作为守护进程运行的,如果你想让它在后台运行,你就把它改成 yes。
pidfile/data/redis/cluster/6382/redis-6382.pid当redis作为守护进程运行的时候,默认情况下它会写一个 pid 到 /var/run/redis.pid 文件里面
port6382监听端口号,默认为 6379,如果你设为 0 ,redis 将不在 socket 上监听任何客户端连接。
database1设置数据库的数目。
cluster-enabledno启用或停用集群
cluster-config-file/data/redis/cluster/6382/nodes-6382.conf集群配置文件,启动时自动生成
cluster-node-timeout15000节点互联超时时间,单位毫秒
cluster-migration-barrier1这个参数表示的是,一个主节点在拥有多少个好的从节点的时候就要割让一个从节点出来。
cluster-require-full-coverageyes如果集群中某些key space没有被集群中任何节点覆盖,集群将停止接受写入
appendonlyyes启用aof持久化方式
dir/data/redis/cluster/6382/节点数据持久化存放目录
slaveof192.168.199.126 6379Master节点配置

如果配置成功并启动,通过客户端工具连接到Master时,输入如下命令

info replication

将会得到如下结果

# Replication
role:master
connected_slaves:3
slave0:ip=192.168.199.249,port=6384,state=online,offset=1127147,lag=1
slave1:ip=192.168.199.249,port=6383,state=online,offset=1127292,lag=1
slave2:ip=192.168.199.249,port=6382,state=online,offset=1127292,lag=1
master_replid:beba8e13c44c5a4afcf8c82889b524b2f76faa22
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1127292
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:78717
repl_backlog_histlen:1048576

Redis Sentinel部署

3个Redis Sentinel信息如下

IPPort备注
192.168.199.12626379Sentinel
192.168.199.12626479Sentinel
192.168.199.12626579Sentinel

Redis安装完成之后,在redis目录(本实例是redis-4.0.1)下会有一个sentinel.conf文件,创建sentinel目录并将该配置文件拷贝进去重命名为自定义的sentinel配置文件。

mkdir /data/redis/sentinel
cp /data/redis/redis-4.0.1/sentinel.conf  /data/redis/sentinel/sentinel_26379.conf
cp /data/redis/redis-4.0.1/sentinel.conf  /data/redis/sentinel/sentinel_26479.conf
cp /data/redis/redis-4.0.1/sentinel.conf  /data/redis/sentinel/sentinel_26579.conf

sentinel_26379.conf配置内容:

protected-mode no
port 26379
sentinel monitor mymaster 192.168.199.249 6384 2
sentinel failover-timeout mymaster 60000
sentinel config-epoch mymaster 1
sentinel leader-epoch mymaster 1
sentinel known-slave mymaster 192.168.199.126 6379

sentinel_26479.conf配置内容:

protected-mode no
port 26479
sentinel monitor mymaster 192.168.199.249 6384 2
sentinel failover-timeout mymaster 60000
sentinel config-epoch mymaster 1
sentinel leader-epoch mymaster 1
sentinel known-slave mymaster 192.168.199.126 6379

sentinel_26579.conf配置内容:

protected-mode no
port 26579
sentinel monitor mymaster 192.168.199.249 6384 2
sentinel failover-timeout mymaster 60000
sentinel config-epoch mymaster 1
sentinel leader-epoch mymaster 1
sentinel known-slave mymaster 192.168.199.126 6379

启动Redis Master实例

进入Redis Master服务器Redis bin目录,执行以下命令:

./redis-server /data/redis/redis-4.0.1/redis.conf

启动Redis Slave实例

进入Redis Slave服务器Redis bin目录,执行以下命令:

./redis-server /data/redis/cluster/6382/redis-6382.conf
./redis-server /data/redis/cluster/6383/redis-6383.conf
./redis-server /data/redis/cluster/6384/redis-6384.conf

启动Sentinel实例

进入Redis Slave服务器Redis bin目录,执行以下命令:

./redis-sentinel /data/redis/sentinel/sentinel_26379.conf 
./redis-sentinel /data/redis/sentinel/sentinel_26479.conf 
./redis-sentinel /data/redis/sentinel/sentinel_26579.conf 

如果启动时出现如下信息,表示启动成功

这里写图片描述

Maven配置

<dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.9.0</version>
    </dependency>       
    <dependency>
         <groupId>org.springframework.data</groupId>
         <artifactId>spring-data-redis</artifactId>
          <version>1.7.5.RELEASE</version>
</dependency>

在config.properties中增加如下配置

#Redis sentinel使用
redis.sentinel1.host=192.168.199.126
redis.sentinel1.port=26379
redis.sentinel2.host=192.168.199.126
redis.sentinel2.port=26479
redis.sentinel3.host=192.168.199.126
redis.sentinel3.port=26579
redis.sentinel.masterName=mymaster

新建spring-redis-sentinel.xml配置文件,写入如下信息

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util"
    xmlns:task="http://www.springframework.org/schema/task" xmlns:cache="http://www.springframework.org/schema/cache"
    xmlns:c='http://www.springframework.org/schema/c' xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd
        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd
        http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-3.1.xsd"
    default-lazy-init="true">

    <!-- 开启spring cache注解功能 -->
    <cache:annotation-driven cache-manager="redisCacheManager" />

    <context:annotation-config />

    <context:property-placeholder
        ignore-unresolvable="true" location="classpath:config.properties" />

    <!-- Redis -->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxTotal" value="${redis.maxTotal}" />
        <property name="maxIdle" value="${redis.maxIdle}" />
        <property name="maxWaitMillis" value="${redis.maxWait}" />
        <property name="testOnBorrow" value="${redis.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="${redis.sentinel.masterName}"></property>
            </bean>
        </property>
        <property name="sentinels">
            <set>
                <bean class="org.springframework.data.redis.connection.RedisNode">
                    <constructor-arg name="host" value="${redis.sentinel1.host}"></constructor-arg>
                    <constructor-arg name="port" value="${redis.sentinel1.port}"></constructor-arg>
                </bean>
                <bean class="org.springframework.data.redis.connection.RedisNode">
                    <constructor-arg name="host" value="${redis.sentinel2.host}"></constructor-arg>
                    <constructor-arg name="port" value="${redis.sentinel2.port}"></constructor-arg>
                </bean>
                <bean class="org.springframework.data.redis.connection.RedisNode">
                    <constructor-arg name="host" value="${redis.sentinel3.host}"></constructor-arg>
                    <constructor-arg name="port" value="${redis.sentinel3.port}"></constructor-arg>
                </bean>
            </set>
        </property>
    </bean>

    <!-- redis服务器中心 -->
    <bean id="jedisConnectionFactory"
        class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <constructor-arg name="sentinelConfig" ref="sentinelConfiguration"></constructor-arg>
        <constructor-arg name="poolConfig" ref="poolConfig"></constructor-arg>
    </bean>

    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFactory" />
        <property name="keySerializer">
            <bean
                class="org.springframework.data.redis.serializer.StringRedisSerializer" />
        </property>
        <property name="valueSerializer">
            <bean
                class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
        </property>
    </bean>

    <!-- redis缓存管理器 -->
    <bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
        <constructor-arg name="redisOperations" ref="redisTemplate" />
    </bean>

    <bean id="redisUtils" class="ron.blog.blog_service.utils.RedisUtils" />
</beans>

在spring-context.xml引入spring-redis-sentinel.xml

<import resource="classpath:spring-redis-sentinel.xml" />

编写Redis帮助类RedisUtils

public class RedisUtils {
    /** 
     * RedisTemplate是一个简化Redis数据访问的一个帮助类, 
     * 此类对Redis命令进行高级封装,通过此类可以调用ValueOperations和ListOperations等等方法。 
     */  
    @Autowired  
    private RedisTemplate<Serializable, Object> redisTemplate;  

    /** 
     * 批量删除对应的value 
     *  
     * @param keys 
     */  
    public void remove(final String... keys) {  
        for (String key : keys) {  
            remove(key);  
        }
    }  

    /** 
     * 批量删除key 
     *  
     * @param pattern 
     */  
    public void removePattern(final String pattern) {  
        Set<Serializable> keys = redisTemplate.keys(pattern);  
        if (keys.size() > 0)  
            redisTemplate.delete(keys);  
    }  

    /** 
     * 删除对应的value 
     * @param key 
     */  
    public void remove(final String key) {  
        if (exists(key)) {  
            redisTemplate.delete(key);  
        }  
    }  

    /** 
     * 缓存是否存在
     * @param key 
     * @return 
     */  
    public boolean exists(final String key) {  
        return redisTemplate.hasKey(key);  
    }  

    /** 
     * 读取缓存 
     * @param key 
     * @return 
     */  
    public Object get(final String key) {  
        Object result = null;  
        ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();  
        result = operations.get(key);  
        return result;  
    }  

    /** 
     *  
     * @Author Ron
     * @param key 
     * @param hashKey 
     * @return 
     */  
    public Object get(final String key, final String hashKey){  
        Object result = null;  
        HashOperations<Serializable,Object,Object> operations = redisTemplate.opsForHash();  
        result = operations.get(key, hashKey);  
        return result;  
    }  

    /** 
     * 写入缓存 
     *  
     * @param key 
     * @param value 
     * @return 
     */  
    public boolean set(final String key, Object value) {  
        boolean result = false;  
        try {  
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();  
            operations.set(key, value);  
            result = true;  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        return result;  
    }  

    /** 
     *  
     * @Author Ron 
     * @param key 
     * @param hashKey 
     * @param value 
     * @return 
     */  
    public boolean set(final String key, final String hashKey, Object value) {  
        boolean result = false;  
        try {  
            HashOperations<Serializable,Object,Object> operations = redisTemplate.opsForHash();  
            operations.put(key, hashKey, value);  
            result = true;  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        return result;  
    }  

    /** 
     * 写入缓存 
     *  
     * @param key 
     * @param value 
     * @return 
     */  
    public boolean set(final String key, Object value, Long expireTime) {  
        boolean result = false;  
        try {  
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();  
            operations.set(key, value);  
            redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);  
            result = true;  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        return result;  
    }  
}

以上的所有配置,如果不出问题,那么基本上我们已经集成了Spring与Redis Sentinel,接下来就是在我们的例子中使用。

在我们的Blog服务(BlogService)中,我们自动装配RedisUtils实例,在进入博客详细页面时,我们首先到Redis中获取是否存在我们所要查看的博客,如果没有,则从MySQL获取并写入Redis。

    @Autowired 
    private BlogContentMapper blogContentMapper;//读者自行定义

    @Autowired  
    RedisUtils redisUtils;  
    /**
     * @Comment 获取博客内容
     * @Author Ron
     * @Date 2017年10月25日 下午3:05:27
     * @return
     */
    @Override
    public BlogContent getBlog(String bid) {
        if(redisUtils.exists(bid)){
            logger.info("缓存命中博客"+bid);
            return (BlogContent) redisUtils.get(bid);
        }else{
            logger.info("缓存尚未命中博客"+bid);
            BlogContent blogContent = blogContentMapper.selectByPrimaryKey(bid);
            redisUtils.set(bid, blogContent);
            return blogContent;
        }
    }

实例演示

我们第一次进入指定博客页面时会在控制台打出缓存尚未命中博客的信息,第二次进入则会打出缓存命中博客字样。

博客详细页面:

这里写图片描述

第一次控制台打出的信息:

这里写图片描述

第二次刷新页面是空值台打出的信息:

这里写图片描述

先查看一下Sentinel信息:
输入命令:./redis-cli -p 26379 info Sentinel

# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=192.168.199.126:6379,slaves=3,sentinels=4

我们可以看到Master的名称、ip地址、Slave的数量、Sentinel数量。

我们停掉一个Sentinel,在我们的控制台中就会监控到并出现连接丢失的错误日志,但是我们的系统仍然可用。

这里写图片描述

重新启动之后报错日志就停止。

我们停掉Master,在Sentinel中就会重新选举一个Slave为Master,同时在我们的Eclipse的控制台也可以看到重新选举的Master;

这里写图片描述

我们看到系统重新选举192.168.199.126 6382为Master。

注意:
启动服务时,可能会报连接Redis Sentinel超时,此时可能是防火墙未关闭造成,使用如下命令关闭防火墙即可:

在CentOS 7中默认使用firewall做为防火墙,下面是启动&关闭防火墙的命令:

// 启动firewall
systemctl start firewalld.service
// 关闭firewall
systemctl stop firewalld.service
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

RonTech

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值