Springboot 整合Redis/Codis 之统一API接
2018-12-11 本文已影响53人
步闲
前言
我们知道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包。