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

来源:互联网 发布:网络mc第一红人比赛 编辑:程序博客网 时间:2024/05/21 08:04

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实例信息如下:

IP Port 备注 192.168.199.126 6379 Master 192.168.199.249 6382 Slave 192.168.199.249 6383 Slave 192.168.199.249 6384 Slave

配置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/clustercd /data/redis/clustermkdir -p 6382cp /data/redis/redis-4.0.1/redis.conf /data/redis/cluster/6382/redis-6382.confmkdir -p 6383cp /data/redis/redis-4.0.1/redis.conf /data/redis/cluster/6383/redis-6383.confmkdir -p 6384cp /data/redis/redis-4.0.1/redis.conf /data/redis/cluster/6384/redis-6384.conf

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

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

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

info replication

将会得到如下结果

# Replicationrole:masterconnected_slaves:3slave0:ip=192.168.199.249,port=6384,state=online,offset=1127147,lag=1slave1:ip=192.168.199.249,port=6383,state=online,offset=1127292,lag=1slave2:ip=192.168.199.249,port=6382,state=online,offset=1127292,lag=1master_replid:beba8e13c44c5a4afcf8c82889b524b2f76faa22master_replid2:0000000000000000000000000000000000000000master_repl_offset:1127292second_repl_offset:-1repl_backlog_active:1repl_backlog_size:1048576repl_backlog_first_byte_offset:78717repl_backlog_histlen:1048576

Redis Sentinel部署

3个Redis Sentinel信息如下

IP Port 备注 192.168.199.126 26379 Sentinel 192.168.199.126 26479 Sentinel 192.168.199.126 26579 Sentinel

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

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

sentinel_26379.conf配置内容:

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

sentinel_26479.conf配置内容:

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

sentinel_26579.conf配置内容:

protected-mode noport 26579sentinel monitor mymaster 192.168.199.249 6384 2sentinel failover-timeout mymaster 60000sentinel config-epoch mymaster 1sentinel leader-epoch mymaster 1sentinel 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.126redis.sentinel1.port=26379redis.sentinel2.host=192.168.199.126redis.sentinel2.port=26479redis.sentinel3.host=192.168.199.126redis.sentinel3.port=26579redis.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

# Sentinelsentinel_masters:1sentinel_tilt:0sentinel_running_scripts:0sentinel_scripts_queue_length:0sentinel_simulate_failure_flags:0master0: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做为防火墙,下面是启动&关闭防火墙的命令:

// 启动firewallsystemctl start firewalld.service// 关闭firewallsystemctl stop firewalld.service
阅读全文
0 0
原创粉丝点击