说明
通过本篇文章记录总结下在springboot中使用redis,还有在工作中使用redis时碰到的一些问题。
正文
引入依赖
新建springboot项目,引入redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置redis
这里有两种配置方式,一种是通过在application.properties中直接配置redis,另一种就是通过创建redis.propertiesp配置文件。
创建redis.properties
redis.server.host=localhost
redis.server.port=6379
reids.server.timeout=2000
redis.pool.maxIdle=50
redis.pool.minIdle=1
redis.pool.maxWait=1000
redis.pool.maxTotal=-1
redis.pool.testWhileIdle=true
创建配置的的相关类RedisConn和RedisConfig
RedisConn
public class RedisConn {
private String host;
private int port;
private int timeout;
....省略setter和getter
}
RedisConfig
@Configuration
@PropertySource(value = "redis.properties")
public class RedisConfig {
@Bean
@ConfigurationProperties(prefix = "redis.pool")
public JedisPoolConfig jedisPoolConfig(){
return new JedisPoolConfig();
}
@Bean
@ConfigurationProperties(prefix = "redis.server")
public RedisConn redisConn(){
return new RedisConn();
}
@Bean
public JedisConnectionFactory jedisConnectionFactory(@Qualifier("jedisPoolConfig") JedisPoolConfig jedisPoolConfig,
@Qualifier("redisConn") RedisConn redisConn){
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
jedisConnectionFactory.setPoolConfig(jedisPoolConfig);
jedisConnectionFactory.setHostName(redisConn.getHost());
jedisConnectionFactory.setPort(redisConn.getPort());
jedisConnectionFactory.setTimeout(redisConn.getTimeout());
return jedisConnectionFactory;
}
@Bean(name = "redisTemplate")
public RedisTemplate<String,Object> redisTemplate(@Qualifier("jedisConnectionFactory") JedisConnectionFactory jedisConnectionFactory){
RedisTemplate<String,Object> template = new RedisTemplate<>();
template.setConnectionFactory(jedisConnectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
return template;
}
}
在application.properties中配置
#Redis数据库的默认索引 默认为0
spring.redis.database=0
#Redis服务器地址
spring.redis.host=localhost
#Redis服务器连接端口
spring.redis.port=6379
#Redis服务器连接密码(默认为空)
spring.redis.password=
#连接池的最大连接数 (使用负值表示没有限制)
spring.redis.pool.max-active=8
#连接池最大阻塞时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
#连接池中最大的空闲连接
spring.redis.pool.max-idle=8
#连接池中最小的空闲连接
spring.redis.pool.min-idle=0
#连接超时时间(毫秒)
spring.redis.timeout=1000
Redis的使用
通过上述配置,我们可以通过RedisTemplate来对redis操作,但是存储的value都是String类型的,如果要存储一个对象,就必须将其转为json或实现关于对象的RedisSerializer。这里通过创建ObjectSerializer来实现对象的存取。
ObjectSerializer
public class ObjectSerializer implements RedisSerializer<Object> {
private Converter<Object,byte[]> serializer = new SerializingConverter();
private Converter<byte[],Object> deserializer = new DeserializingConverter();
static final byte[] EMPTY_APPAY = new byte[0];
@Override
public byte[] serialize(Object o) throws SerializationException {
if(o == null){
return EMPTY_APPAY;
}
return serializer.convert(o);
}
@Override
public Object deserialize(byte[] bytes) throws SerializationException {
if(isEmpty(bytes)){
return null;
}
return deserializer.convert(bytes);
}
}
创建好类后需要对其进行配置,在RedisConfig中配置:
@Bean(name = "userRedisTemplate")
public RedisTemplate<String, User> userRedisTemplate(@Qualifier("jedisConnectionFactory") JedisConnectionFactory jedisConnectionFactory){
RedisTemplate<String,User> template = new RedisTemplate<>();
template.setConnectionFactory(jedisConnectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new ObjectSerializer());
return template;
}
测试:
@Test
public void testRedisUser(){
User user = new User();
user.setName("zhangsan");
user.setAge(23);
user.setGender("male");
userRedisTemplate.opsForValue().set(user.getName(),user,3600,TimeUnit.SECONDS);
}
相关问题
在开发中,使用redis的zset来实现某个排行需求,但是在内测过程中发现相同score的数据的时间信息对不上,在开发时直接使用了sort sets的revrange命令来得到数据的逆序,通过查询资料,该命令会根据index和score来对数据逆序,也就是相同score的情况下,最新添加的数据通过revrange命令得到的排序会在相同score的第一位。但实际情况是相同分数的时间是错乱的,而且时间差很大。
在遇到这个问题时,以为是命令的问题,通过jedis来连接redis并操作数据,查看源码后发现,jedis的revrange命令的结果居然是使用set类型接收。想到排序后的数据再放入set集合,肯定会导致数据排序失效,(心想这是不是jedis的bug…)但是经过大量的测试,返回的数据没有任何问题,也就是jedis在这个命令使用set接收结果没有任何问题(好吧…)又重新去看源码,发现此set并非彼set,就是一个披着set外衣的list…应该是jedis为了返回类型与数据类型相统一做了一层封装。
jedis使用SetFromList类来封装结果,使用SetFromList.of(List<E> list)方法封装。
SetFromList类继承了AbstractSet,但是内部有真正存储数据的成员变量的list :
protected static class SetFromList<E> extends AbstractSet<E> implements Serializable {
private static final long serialVersionUID = -2850347066962734052L;
private final transient List<E> list;
private SetFromList(List<E> list) {
if (list == null) {
throw new NullPointerException("list");
} else {
this.list = list;
}
}
......
}
所以revrange返回的set只是一个内部有真正存储数据list的外壳。阅读完源码后清楚命令没有问题,那到底是什么原因导致的数据的时间错乱,之后又进行了简单的测试,这次从数据的添加入手,使用for循环顺序添加100个数据 value为0-99,score相同,结果发现value的排序并没有以0-99的顺序出现,出现了顺序错乱。我猜测应该是在并发情况下,redis是单线程操作,导致数据的实际添加顺序并不是按照调用方法的顺序。但是为什么数据时间差会那么大相差几个小时?
解决数据时间错乱的方法
将revrange得到数据再次排序,根据value(id)的值得到用户相关数据后,根据用户的提交时间进行降序,再根据用户的得分进行降序,这样就得到用户得分排行榜,并且在具有相同分数时,最新提交的在第一位。
源码地址:https://github.com/Edenwds/springboot_study/tree/master/redis
参考资料:https://www.tutorialspoint.com/spring_boot/spring_boot_database_handling.htm
http://blog.didispace.com/springbootredis/