常见延迟队列实现方式
延迟队列的实现方式有很多种,通过程序的方式实现,例如 JDK 自带的延迟队列 DelayQueue,通过 MQ 框架来实现,例如 RocketMQ、RabbitMQ等,本文通过 Redis 的方式来实现延迟队列 。
Redis 是通过有序集合(ZSet)的方式来实现延迟消息队列的,ZSet 有一个 Score 属性可以用来存储延迟执行的时间。
优点:
灵活方便,Redis 是互联网公司的标配,无序额外搭建相关环境;
可进行消息持久化,大大提高了延迟队列的可靠性;
分布式支持,不像 JDK 自身的 DelayQueue;
高可用性,利用 Redis 本身高可用方案,增加了系统健壮性。
缺点:
需要使用无限循环的方式来执行任务检查,会消耗少量的系统资源。
redis的失效监听事件会存在一定的时间差,并且当数据量越大时,误差会越大。
分布式业务的场景下,会出现重复消费的问题。(可以增加分布式锁的实现,但是redisson分布式锁提供了另一种延迟队列的实现方式)
延迟消息主体
/**
* 延迟消息主体
*
* @author cheneq
*/
@Data
public class DelayMessage implements Serializable {
/**
* 消息队列组
*/
private String groupName;
/**
* 消息id
*/
private String id;
/**
* 消息体,对应业务内容
*/
private Object body;
/**
* 消息过期时间 单位:秒
*/
private long expireTime;
}
延迟消息工具类
/**
* 延迟消息工具类
*
* @author cheneq
*/
@Component
public class DelayMessageUtils {
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* 存入消息池
*
* @param delayMessage
*/
public void addMsgPool(DelayMessage delayMessage) {
if (null != delayMessage) {
ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
String key = RedisKey.MESSAGE_POOL + delayMessage.getGroupName() + delayMessage.getId();
valueOperations.set(key, JSON.toJSONString(delayMessage), delayMessage.getExpireTime(), TimeUnit.SECONDS);
}
}
/**
* 从消息池中删除消息
*
* @param groupName
* @param id
*/
public void delMsgPool(String groupName, String id) {
String key = RedisKey.MESSAGE_POOL + groupName + id;
redisTemplate.delete(key);
}
/**
* 向队列中添加消息
*
* @param key
* @param val
* @param score 优先级
* @return 返回消息id
*/
public void addMsgQueue(String key, String val, long score) {
ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
zSetOperations.add(key, val, score);
}
/**
* 从队列删除消息
*
* @param id
* @return
*/