**(2018-07-23更新:
本案例使用的解决方案以及代码,在较大数据量的操作情境下存在严重redis性能问题——大集合的一次性删除操作可能导致redis阻塞,正常业务将无法访问、操作redis。如果需要使用相关功能,请使用资料中的1方法或自行优化redis删除队列操作的执行时间)**
问题:
项目需要为每个用户维护一个列表,存放一些数据。列表中的值有过期时间,过期的值查询可以找到也可以找不到,还会有一个验证,所以无所谓。但是redis队列只有一个整体的过期功能,没有每个元素的单独过期功能,所以如果用户一直不停向队列塞东西,队列就会变的越来越大。这显然不合理。
资料:
https://stackoverflow.com/questions/16545321/how-to-expire-the-hset-child-key-in-redis
https://quickleft.com/blog/how-to-create-and-expire-list-items-in-redis/
查了一下资料,目前给队列、集合元素单独设置过期不可能做到(redis4.0.2)。但是有其他方法可以做到类似功能。
参考的一篇文章提出两种方法:
1.使用SortedSet,使用score参数代表unix时间,程序定期使用ZRANGEBYSCORE清除过期项
2.将集合拆分成多个按时间排序、自动过期的小集合
1方法显然更方便、高效,但是项目必须跑程序为每个集合定期维护,可能产生很多不必要的麻烦,所以我选择2解决方法。
解决方案:
(重要的话说三遍:这个解决方案中过期的值有可能被返回,过期的值有可能被返回,过期的值有可能被返回。如果需要返回确定不过期的值,请在value中加unix时间作验证)
使用一个工具类封装redis操作,自动进行redis集合的拆分和查询。直接上代码:
必须获得redis读、写、设置过期、查询key是否存在 函数
expire表示过期时间(秒); blockSize表示分块大小(秒),不能大于expire
package com.study.javaweb.test1.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* User: longjuanfeng Date: 2017-11-29
*/
public class RedisAutoExpireUtils<ValueType, SetResponse> {
private Logger logger = LoggerFactory.getLogger(RedisAutoExpireUtils.class);
private RedisSetter<ValueType, SetResponse> redisSetter;
private RedisGetter<ValueType> redisGetter;
private RedisExpire redisExpire;
private RedisExists redisExists;
private Integer expire;
//default size is expire
private Integer blockSize;
public RedisAutoExpireUtils(RedisSetter<ValueType, SetResponse> redisSetter, RedisGetter<ValueType> redisGetter, RedisExpire redisExpire, RedisExists redisExists, Integer expire) throws Exception {
this(redisSetter, redisGetter, redisExpire, redisExists, expire, expire);
}
public RedisAutoExpireUtils(RedisSetter<ValueType, SetResponse> redisSetter, RedisGetter<ValueType> redisGetter, RedisExpire redisExpire, RedisExists redisExists, Integer expire, Integer blockSize) throws Exception {
if (blockSize > expire) {
throw new Exception("blockSize should not larger than expire");
}
this.redisSetter = redisSetter;
this.redisGetter = redisGetter;
this.redisExpire = redisExpire;
this.redisExists = redisExists;
this.expire = expire;
this.blockSize = blockSize;
}
public SetResponse setRedisValue(String key, ValueType value) {
Integer nowTime = new Long(System.currentTimeMillis() / 1000).intValue();
Integer blockTail = nowTime % blockSize;
Integer stampPos = nowTime - blockTail;
String timeStamp = String.valueOf(stampPos);
logger.info("timeStamp of {} is {}", nowTime, timeStamp);
String keyWithStamp = key + ":" + timeStamp;
Boolean exists = redisExists.exists(keyWithStamp) == 1;
SetResponse result = redisSetter.addValue(keyWithStamp, value);
if (!exists) {
Integer expireTime = expire + blockSize - blockTail;
redisExpire.setExpire(keyWithStamp, expireTime);
logger.info("set expire of {} is {}", nowTime, expireTime);
}
return result;
}
public ValueType getRedisValue(String key) {
Integer nowTime = new Long(System.currentTimeMillis() / 1000).intValue();
Integer checkBlockNum = expire / blockSize + 1;
Integer blockTail = nowTime % blockSize;
Integer stampPos = nowTime - blockTail;
for (int i = 0; i < checkBlockNum; i++) {
String timeStamp = String.valueOf(stampPos);
logger.info("check timeStamp of {} is {}", nowTime, timeStamp);