11、Spring技术栈-整合Redis,通过Redis的Master-Slave实现缓存数据读写分离

1、Redis主从复制(Master-Salve Replication)简介

Redis 支持简单且易用的主从复制(master-slave replication)功能, 该功能可以让从服务器(slave server)成为主服务器(master server)的精确复制品。

以下是关于 Redis 复制功能的几个重要方面:

  1. Redis 使用异步复制。 从 Redis 2.8 开始, 从服务器会以每秒一次的频率向主服务器报告复制流(replication stream)的处理进度。

  2. 一个主服务器可以有多个从服务器。

  3. 不仅主服务器可以有从服务器, 从服务器也可以有自己的从服务器, 多个从服务器之间可以构成一个图状结构。

  4. 复制功能不会阻塞主服务器: 即使有一个或多个从服务器正在进行初次同步, 主服务器也可以继续处理命令请求。

  5. 复制功能也不会阻塞从服务器: 只要在 redis.conf 文件中进行了相应的设置, 即使从服务器正在进行初次同步, 服务器也可以使用旧版本的数据集来处理命令查询。

  6. 不过, 在从服务器删除旧版本数据集并载入新版本数据集的那段时间内, 连接请求会被阻塞。

  7. 你还可以配置从服务器, 让它在与主服务器之间的连接断开时, 向客户端发送一个错误。

  8. 复制功能可以单纯地用于数据冗余(data redundancy), 也可以通过让多个从服务器处理只读命令请求来提升扩展性(scalability): 比如说, 繁重的 SORT 命令可以交给附属节点去运行。

  9. 可以通过复制功能来让主服务器免于执行持久化操作: 只要关闭主服务器的持久化功能, 然后由从服务器去执行持久化操作即可。

更多请参考:Redis主从(Master-Slave)复制(Replication)设置

2、实例介绍

在(Spring技术栈-整合Redis,使用RedisTemplate实现数据缓存实战)一文中我们已经介绍如何整合Redis,使用RedisTemplate实现数据的缓存。本文将在此基础上通过修改,实现缓存数据的读写分离。

首先需要安装两台虚拟机(本文只讲述一主一从的方式,关于一主多从的方式,各位可自行研究)并在虚拟机上安装Redis,具体如何将Redis配置成为主从复制(Master-Salve Replication)模式,请参考Redis主从(Master-Slave)复制(Replication)设置

具体场景描述:以博客系统为例,用户在发布博客时,系统将博客写入Redis,在详细页面读取博客时,系统从Redis中读取博客信息展示。其中写入时写入到的是Master服务器,读则是从Slave服务器读取。

2.1 在resources目录新建spring-context-redis-ms.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>  

    <!-- redis主服务器中心 -->  
    <bean id="jedisConnectionFactory"  class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" >  
          <property name="poolConfig" ref="poolConfig" />  
          <property name="port" value="${redis.master.port}" />  
          <property name="hostName" value="${redis.master.host}" />
          <property name="timeout" value="${redis.timeout}" ></property>  
    </bean>
    <!-- redis从服务器中心 -->  
    <bean id="slaveJedisConnectionFactory"  class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" >  
          <property name="poolConfig" ref="poolConfig" />  
          <property name="port" value="${redis.slave.port}" />  
          <property name="hostName" value="${redis.slave.host}" />
          <property name="timeout" value="${redis.timeout}" ></property>  
    </bean>

    <!-- 主RedisTemplate -->
    <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>
    <!-- 从RedisTemplate -->
    <bean id="slaveRedisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="slaveJedisConnectionFactory" />
        <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>
    <!-- 从redis缓存管理器 -->
    <bean id="slaveRedisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
        <constructor-arg name="redisOperations" ref="slaveRedisTemplate" />
    </bean>

    <bean id="redisUtils" class="ron.blog.blog_service.utils.MsRedisUtils">
        <constructor-arg name="redisTemplate" ref="redisTemplate" />
        <constructor-arg name="slaveRedisTemplate" ref="slaveRedisTemplate" />
    </bean>  
</beans>

在以上的配置文件中,我们配置了主(Master)服务器和从(Slave)服务器的相关信息,并配置了id为redisTemplate的RedisTemplate对象来操作主(Master)服务器,id为slaveRedisTemplate的RedisTemplate对象来操作从(Slave)服务器。所有对Redis的操作我们都是通过一个叫做MsRedisUtils的帮助类来完成,该类的对象初始化时,我们往构造函数中传入主从(Master-Slave)服务器的RedisTemplate操作对象。

2.2 MsRedisUtils类构建如下

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

    MsRedisUtils(RedisTemplate<Serializable, Object> redisTemplate,RedisTemplate<Serializable, Object> slaveRedisTemplate){
        this.redisTemplate=redisTemplate;
        this.slaveRedisTemplate=slaveRedisTemplate;
    }

    /** 
     * 写入缓存 
     *  
     * @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;  
    } 

    /** 
     * 批量删除对应的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 slaveRedisTemplate.hasKey(key);  
    }  

    /** 
     * 读取缓存 
     * @param key 
     * @return 
     */  
    public Object get(final String key) {  
        Object result = null;  
        ValueOperations<Serializable, Object> operations = slaveRedisTemplate.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 = slaveRedisTemplate.opsForHash();  
        result = operations.get(key, hashKey);  
        return result;  
    }  
}

在该类中,所有写操作(set、update、delete)都使用redisTemplate进行操作,所有的读操作(get)都是用slaveRedisTemplate进行操作。

2.3 在spring-context.xml中导入配置信息

在spring-context.xml文件中,加入如下配置信息:

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

2.4 使用MsRedisUtils类操作Redis

在服务中(本实例是BlogService类),使用MsRedisUtils作为Redis的操作类,实现往Redis服务器写入和读取数据的功能。

    @Autowired  
    MsRedisUtils redisUtils;  

    /**
     * @Comment 添加博客内容
     * @Author Ron
     * @Date 2017年10月25日 下午2:57:51
     * @return
     */
    @Override
    public Resp insertBlog(BlogContent blogContent) {
        String bid = IdGenerator.genUUID();
        blogContent.setBid(bid);
        blogContent.setCrtTime(new Date());
        blogContentMapper.insert(blogContent);
        redisUtils.set(bid, blogContent);
        return new Resp(ResCode.SUCCESS, "");
    }

    /**
     * @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;
        }
    }

源码请见:http://download.csdn.net/download/zyhlwzy/10116300

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

RonTech

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

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

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

打赏作者

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

抵扣说明:

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

余额充值