Springboot 整合Redis/Codis 之统一API接口配置化

96
步闲
2.2 2018.12.11 14:56* 字数 446
前言

我们知道Springboot整合了RedisTemplate模板对外提供redis服务,应该说是spring官方比较推荐的方式。但在实际使用过程中,个人觉得并不是很方便,主要原因有如下几点:

  • API 变化比较大,学习成本略高
  • 切换库比较麻烦些,需要自己写多行代码实现
  • Springboot 并未整合codisTemplate,如果在项目中需要由redis切换至codis,业务代码需要修改

基于以上几点,本文在保留了Springboot RedisTemplate模板的同时,整合了Redis 和 Codis 原生API接口统一对外提供服务。开发者只需拿到cache client 进行相关操作即可,所以参数全部配置化。

下面我们来迅速看一下。

一. Springboot集成RedisTemplate模板方式

优点:支持对象映射存储,自动转换为Json存储,无需手动转为String再存储

配置类如下:

package com.xcar.hbase.rest.connection;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;

@Configuration
public class SpringRedisTemplate extends CachingConfigurerSupport {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private int port;

    @Value("${spring.redis.timeout}")
    private int timeout;

    @Value("${spring.redis.database}")
    private int database;

    @Value("${spring.redis.password}")
    private String password;

    @Value("${spring.redis.pool.max-active}")
    private int max_active;

    @Value("${spring.redis.pool.max-idle}")
    private int max_idle;

    @Value("${spring.redis.pool.min-idle}")
    private int min_idle;

    @Value("${spring.redis.pool.max-wait}")
    private long max_wait;



    /**
     * 连接redis的工厂类
     * @return
     */
    @Bean
    public JedisConnectionFactory jedisConnectionFactory() {
        JedisConnectionFactory factory = new JedisConnectionFactory();
        factory.setHostName(host);
        factory.setPort(port);
        factory.setTimeout(timeout);
        factory.setPassword(password);
        factory.setDatabase(database);
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(max_idle);
        jedisPoolConfig.setMaxTotal(max_active);
        jedisPoolConfig.setMinIdle(min_idle);
        jedisPoolConfig.setMaxWaitMillis(max_wait);
        jedisPoolConfig.setTestOnBorrow(true);
        jedisPoolConfig.setTestOnReturn(true);
        jedisPoolConfig.setBlockWhenExhausted(false);
        factory.setPoolConfig(jedisPoolConfig);
        return factory;
    }

    /**
     * 配置RedisTemplate
     * 设置添加序列化器
     * key 使用string序列化器
     * value 使用Json序列化器
     * 还有一种简答的设置方式,改变defaultSerializer对象的实现。
     * RedisTemplate提供了以下几种序列化器,我们可以根据项目的需求进行选择。
     * RedisSerializer redis序列化的接口类
     * OxmSerializer xml到object的序列化/反序列化
     * StringRedisSerializer string字符串的序列化/反序列化
     * JacksonJsonRedisSerializer json到object的序列化/反序列化
     * Jackson2JsonRedisSerializer json到object的序列化/反序列化
     * JdkSerializationRedisSerializer java对象的序列化/反序列化
     *
     * @return
     */
    @Bean
    public org.springframework.data.redis.core.RedisTemplate<String, Object> getRedisTemplate() {
        //StringRedisTemplate的构造方法中默认设置了stringSerializer
        org.springframework.data.redis.core.RedisTemplate<String, Object> template = new org.springframework.data.redis.core.RedisTemplate<>();
        //set key serializer
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        template.setKeySerializer(stringRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        //set value serializer
        template.setDefaultSerializer(jackson2JsonRedisSerializer);

        template.setConnectionFactory(jedisConnectionFactory());
        template.afterPropertiesSet();
        return template;
    }
}

Spring 配置文件application.yml配置如下:

spring:
  redis:
    # Redis服务器地址
    host: xx.xx.xx.xx
    # Redis服务器连接端口
    port: xxxx
    # Redis服务器连接密码(默认为空)
    password: *****************
    # Redis数据库索引(默认为0)// 3 号库存标签相关的数据
    database: 3
    # 连接超时时间(毫秒)
    timeout: 60000
    pool:
      # 连接池最大连接数(使用负值表示没有限制)
      max-active: 200
      # 连接池最大阻塞等待时间(使用负值表示没有限制)
      max-wait: 30000
      # 连接池中的最大空闲连接
      max-idle: 50
      # 连接池中的最小空闲连接
      min-idle: 5

如果想在同一个项目中随时切换redis库,需要手动实现一下:

package com.xcar.hbase.rest.utils;

import com.xcar.hbase.rest.common.exception.dls.DlsCacheException;
import io.codis.jodis.JedisResourcePool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

@Component
public class CacheUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private JedisConnectionFactory jedisConnectionFactory;

    /**
     * 指定redis库
     */
    public RedisTemplate getRedisTemplate(int index) {
        return getTemplate(redisTemplate, index);
    }

    /**
     * 获取SpringRedisTemplate
     */
    public RedisTemplate getTemplate(RedisTemplate redisTemplate, int index) {
        jedisConnectionFactory.setDatabase(index);
        redisTemplate.setConnectionFactory(jedisConnectionFactory);
        return redisTemplate;
    }
}

使用示例:

package com.xcar.hbase.rest.controller;

import com.xcar.hbase.rest.utils.CacheUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import redis.clients.jedis.Jedis;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;


@Controller
public class RedisController {

    @Autowired
    private CacheUtil cacheUtil;

    /**
     * 切换库 redisTemplate
     * @param index
     */
    @ResponseBody
    @RequestMapping("/redis/{index}")
    public void stringTest(@PathVariable("index") int index){
        RedisTemplate redisTemplate = cacheUtil.getRedisTemplate(1);  // 随时切换
        ValueOperations<String,Object> valueOperations = redisTemplate.opsForValue();
        valueOperations.set("hello", "redis",30,TimeUnit.SECONDS);
        valueOperations.set("hello2", 123,45,TimeUnit.SECONDS);
        Map<String,String> map = new HashMap<>();
        map.put("a","av");
        map.put("b","bv");
        valueOperations.set("hello3", map,60,TimeUnit.SECONDS);
        System.out.println("useRedisDao = " + valueOperations.get("hello"));
        System.out.println("useRedisDao = " + valueOperations.get("hello2"));
        System.out.println("useRedisDao = " + valueOperations.get("hello3").getClass());
        Map<String, String> mmm = (Map<String, String>) valueOperations.get("hello3");
        System.out.println(mmm.get("a"));
    }
}

可以看出,RedisTemplate api 相对原生api 变化还是很大的。

二. Springboot集成Redis/Codis原生API方式
1. 集成Redis
package com.xcar.hbase.rest.connection;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

@Configuration
public class RedisDataSource{

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private int port;

    @Value("${spring.redis.timeout}")
    private int timeout;

    @Value("${spring.redis.password}")
    private String password;

    @Value("${spring.redis.pool.max-active}")
    private int max_active;

    @Value("${spring.redis.pool.max-idle}")
    private int max_idle;

    @Value("${spring.redis.pool.min-idle}")
    private int min_idle;

    @Value("${spring.redis.pool.max-wait}")
    private long max_wait;



    /**
     * 连接redis的工厂类
     * @return
     */
    @Bean
    public JedisPool getJedisPool() {

        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(max_idle);
        jedisPoolConfig.setMaxTotal(max_active);
        jedisPoolConfig.setMinIdle(min_idle);
        jedisPoolConfig.setMaxWaitMillis(max_wait);
        jedisPoolConfig.setTestOnBorrow(true);
        jedisPoolConfig.setTestOnReturn(true);
        jedisPoolConfig.setBlockWhenExhausted(false);

        return new JedisPool(jedisPoolConfig, host, port, timeout, password);
    }
}
2. 集成Codis
package com.xcar.hbase.rest.connection;

import io.codis.jodis.JedisResourcePool;
import io.codis.jodis.RoundRobinJedisPool;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPoolConfig;

@Configuration
public class CodisDataSource {

    @Value("${spring.codis.zk-addr}")
    private String zkAddr;

    @Value("${spring.codis.zk-proxy-dir}")
    private String zkProxyDir;

    @Value("${spring.codis.password}")
    private String password;

    @Value("${spring.codis.timeout}")
    private int timeout;

    @Value("${spring.codis.pool.max-active}")
    private int max_active;

    @Value("${spring.codis.pool.max-idle}")
    private int max_idle;

    @Value("${spring.codis.pool.min-idle}")
    private int min_idle;

    @Value("${spring.codis.pool.max-wait}")
    private long max_wait;


    @Bean
    public JedisResourcePool getPool() {

        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxIdle(max_idle);
        poolConfig.setMaxTotal(max_active);
        poolConfig.setTestOnBorrow(true); //在borrow一个jedis实例的时候,是否要进行验证操作,如果赋值true。则得到的jedis实例肯定是可以用的。
        poolConfig.setTestOnReturn(true); //在return一个jedis实例的时候,是否要进行验证操作,如果赋值true。则放回jedispool的jedis实例肯定是可以用的。
        poolConfig.setMaxWaitMillis(max_wait);
        poolConfig.setBlockWhenExhausted(false); //连接耗尽的时候,是否阻塞,false 会抛出异常,true 阻塞直到超时。默认为true。

        JedisResourcePool pool = RoundRobinJedisPool.create().poolConfig(poolConfig)
                .curatorClient(zkAddr, timeout)
                .password(password).zkProxyDir(zkProxyDir).build();

        return pool;
    }
}
3. 配置文件

spring:
  redis:
    # Redis服务器地址
    host: xxxxxxxx
    # Redis服务器连接端口
    port: 8888
    # Redis服务器连接密码(默认为空)
    password: *************
    # Redis数据库索引(默认为0)// 3 号库存标签相关的数据
    database: 3
    # 连接超时时间(毫秒)
    timeout: 60000
    pool:
      # 连接池最大连接数(使用负值表示没有限制)
      max-active: 200
      # 连接池最大阻塞等待时间(使用负值表示没有限制)
      max-wait: 30000
      # 连接池中的最大空闲连接
      max-idle: 50
      # 连接池中的最小空闲连接
      min-idle: 5

  codis:
    # Codis服务器地址
    zk-addr: xx.xx.xx.xx:2181,xx.xx.xx.xx:2181,xx.xx.xx.xx:2181
    # Codis服务器连接端口
    zk-proxy-dir: xxxxxxxxx
    # Codis服务器连接密码(默认为空)
    password: ****************
    # Codis数据库索引(默认为0)// 3 号库存标签相关的数据
    database: 3
    # 连接超时时间(毫秒)
    timeout: 60000
    pool:
      # 连接池最大连接数(使用负值表示没有限制)
      max-active: 500
      # 连接池中连接用完时,新的请求最大等待时间,毫秒.即连接池最大阻塞等待时间(使用负值表示没有限制)
      max-wait: 30000
      # 在jedispool中最大的idle状态(空闲的)的jedis实例的个数
      max-idle: 50
      # 在jedispool中最小的idle状态(空闲的)的jedis实例的个数
      min-idle: 5

  # 指定缓存策略(1--->redis; 2--->codis)
  cache.index: 1

通过指定spring.cache.index可以指定使用redis/codis.
通过指定database可设置默认库(后续可任意切换,见下文)。

4. 编写缓存工具类,统一对外接口
package com.xcar.hbase.rest.utils;

import com.xcar.hbase.rest.common.exception.dls.DlsCacheException;
import io.codis.jodis.JedisResourcePool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

@Component
public class CacheUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private JedisConnectionFactory jedisConnectionFactory;

    @Autowired
    private JedisResourcePool jedisResourcePool;

    @Autowired
    private JedisPool jedisPool;

    @Value("${spring.redis.database}")
    private int redis_database;

    @Value("${spring.codis.database}")
    private int codis_database;

    @Value("${spring.cache.index}")
    private int cache_index;

    /**
     * 指定redis库
     */
    public RedisTemplate getRedisTemplate(int index) {
        return getTemplate(redisTemplate, index);
    }

    /**
     * 获取SpringRedisTemplate
     */
    public RedisTemplate getTemplate(RedisTemplate redisTemplate, int index) {
        jedisConnectionFactory.setDatabase(index);
        redisTemplate.setConnectionFactory(jedisConnectionFactory);
        return redisTemplate;
    }

    /**
     * 获取原生redis/codis client
     * @return
     */
    public Jedis getCacheClient(){

        Jedis jedis = null;
        if(cache_index==1){  // jedis 1
            jedis = jedisPool.getResource();
            jedis.select(redis_database);  // 设置默认库
        }else if(cache_index==2){  // codis 2
            jedis = jedisResourcePool.getResource();
            jedis.select(codis_database);  // 设置默认库
        }else {  // exception
            throw new DlsCacheException("please select index in {1:redis,2:codis} ! your select index is : "+cache_index);
        }
        return jedis;
    }
}
5. 访问示例
package com.xcar.hbase.rest.controller;

import com.xcar.hbase.rest.utils.CacheUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import redis.clients.jedis.Jedis;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;


@Controller
public class RedisController {

    @Autowired
    private CacheUtil cacheUtil;

    @ResponseBody
    @RequestMapping("/redis/put")
    public void simpleTest(){

        Jedis cacheClient = null;
        try{
            cacheClient = cacheUtil.getCacheClient();
            cacheClient.set("test123","123");
            cacheClient.expire("test123",30);
            cacheClient.select(1);
            // 进行各种操作.........
        }finally {
            if (cacheClient != null) {
                cacheClient.close();
            }
        }
    }
}

如此,我们再切换codis/redis时就不必再修改任何代码了。

6. 项目 pom 文件
<!--Inherit defaults from Spring Boot-->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.8.RELEASE</version>
</parent>
<!--redis 集成 spring-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!--codis 依赖-->
<dependency>
    <groupId>io.codis.jodis</groupId>
    <artifactId>jodis</artifactId>
    <version>0.4.1</version>
</dependency>

注意:记得去除springboot框架中集成的redis包。

Spring 工作笔记
Gupao