前言
本篇意在使用Redis模拟实现延时队列.
Redis中的有序集合Zset可以实现延时队列,Zset可以看作是缩小版的redis,可以看作是用来存储键值对的集合,是集合名-K-V的结构,在Zset中,会按照Score进行排序。
有序集合中键值对的键被称为成员
,值被称为分值
,分值必须为浮点数。
命令 | 行为 |
ZADD | 将一个带有给定分值的成员添加到有序集合中,返回添加元素的个数 |
ZRANGE | 根据元素在有序排列中的位置,从有序集合里面获取多个元素 |
ZRANGEBYSCORE | 根据一个分值段来获取在该分值段的所有元素 |
ZREM | ZREM key member-------如果给定成员存在于该有序集合,则删除该成员 |
ZCARD | ZCARD key--------返回有序集合包含的成员数量 |
ZCOUNT | ZCOUNT key min max----------返回分值介于min 和max之间的成员数量 |
ZSCORE | ZSOCRE key member --------返回成员的分值 |
ZINCRBY | ZINCRBY key increment member -----将member成员的分值加上increment |
示例
127.0.0.1:6379> ZADD ZZ 728 member1
(integer) 1
127.0.0.1:6379> ZADD ZZ 729 member2
(integer) 1
127.0.0.1:6379> ZADD ZZ 729 member2
(integer) 0
127.0.0.1:6379> ZRANGE ZZ 0 -1
1) "member1"
2) "member2"
127.0.0.1:6379> ZRANGEBYSCORE ZZ 728 728
1) "member1"
127.0.0.1:6379> ZREM ZZ member1
(integer) 1
第三行我们在键为ZZ的有序集合中添加了已经存在的键,所以返回0标识已存在,但是会覆盖原来的值。
第四行我们通过ZRANGE命令来获取下标范围的键值对,这里只返回显示了成员,如果需要返回分值,则在命令后加withscores
即可。
思路
把当前时间戳和延时时间相加,也就是到期时间,存入Redis中,然后不断轮询,找到到期的,拿到再删除即可。
实战
这里使用RedisTemplate作为Redis客户端连接,放在延时队列中的可以看成是一个个延时任务。
为了适配不同类型的对象存入Zset,这里我们使用泛型
/**
* @author 阳光大男孩!!!
*/
@Data
public class DelayTask<T> {
// 消息id
private String id;
// 任务名称
private String taskName;
// 具体任务内容
private T msg;
}
延时队列具体实现,主要分为放任务和轮询任务两个部分,放任务
public class RedisDelayQueue<T> {
/**
* 延迟队列名称
*/
private String delayQueueName = "delayQueue";
// 传入redis客户端操作
public RedisDelayQueue(RedisTemplate redisTemplate,String delayQueueName)
{
this.redisTemplate = redisTemplate;
this.delayQueueName = delayQueueName;
}
/**
* 设置延迟消息
*/
public void setDelayTasks(T msg, long delayTime) {
DelayTask<T> delayTask = new DelayTask<>();
delayTask.setId(UUID.randomUUID().toString());
delayTask.setMsg(msg);
Boolean addResult = redisTemplate.opsForZSet().add(delayQueueName, JSONObject.toJSONString(delayTask), System.currentTimeMillis() + delayTime);
if(addResult)
{
System.out.println("添加任务成功!"+JSONObject.toJSONString(delayTask)+"当前时间为"+ LocalDateTime.now());
}
}
/**
* 监听延迟消息
*/
public void listenDelayLoop() {
while (true) {
// 获取一个到点的消息
Set<String> set = redisTemplate.opsForZSet().rangeByScore(delayQueueName, 0, System.currentTimeMillis(), 0, 1);
// 如果没有,就等等
if (set.isEmpty()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 继续执行
continue;
}
// 获取具体消息的key
String it = set.iterator().next();
// 删除成功
if (redisTemplate.opsForZSet().remove(delayQueueName, it) > 0) {
// 拿到任务
DelayTask delayTask = JSONObject.parseObject(it, DelayTask.class);
// 后续处理
System.out.println("消息到期"+delayTask.getMsg().toString()+",时间为"+LocalDateTime.now());
}
}
}
}