Java 延時(shí)隊(duì)列DelayQueue實(shí)現(xiàn)原理及Demo
在上一篇文章中,我們使用jdk自帶的DelayQueue延時(shí)隊(duì)列寫(xiě)了一個(gè)小demo,現(xiàn)在來(lái)看看使用redis的zset有序集合是怎么實(shí)現(xiàn)延時(shí)隊(duì)列的。在實(shí)現(xiàn)之前,先把zset相關(guān)的指令了解一下。
一、zset集合
zset是一個(gè)有序集合,通過(guò)排序?qū)傩詓core進(jìn)行排序;也就是說(shuō)每個(gè)存儲(chǔ)元素都是由兩個(gè)元素組成,一個(gè)是有序值,另一個(gè)是排序值。
二、zset相關(guān)指令
#往集合里添加元素
zadd 有序集合key 排序值 有序值 排序值 有序值
# 取出有序集合里的所有元素
zrange 有序集合key 0 -1
# 根據(jù)score進(jìn)行刪除
zremrangebyscore 有序集合key 開(kāi)始score 結(jié)束score
參考文檔:https://redis.io/docs/manual/data-types

三、使用zset實(shí)現(xiàn)延遲隊(duì)列
3.1 使用當(dāng)前時(shí)間加上隨機(jī)秒數(shù)作為score分值,往key值為AA的zset中添加數(shù)據(jù);
@Autowired
private RedisTemplate<String,Object> redisTemplate;
// 日期格式化,精確到時(shí)分秒
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* 60秒執(zhí)行一次
*/
@Scheduled(cron = "*/60 * * * * ?")
public void initKeys() {
// zset數(shù)據(jù)添加
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
Random random = new Random();
int score = 0;
Long scheduleTime = 0L;
for(int i=0;i<100;i++){
// 取值1到100的隨機(jī)數(shù)
score = random.nextInt(100);
// 設(shè)置score,在當(dāng)前時(shí)間上加score秒
scheduleTime = System.currentTimeMillis() + (1000) * score;
log.info("AA{}{},過(guò)期時(shí)間:{}",i,score, format.format(new Date(scheduleTime)));
zset.add("AA", "AA" + i + score, scheduleTime);
}
}
3.2 根據(jù)當(dāng)前時(shí)間取出zset中的過(guò)期數(shù)據(jù)并刪除;
// 延遲任務(wù)處理
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
// TimerTask 定時(shí)任務(wù)
new Timer().schedule(new TimerTask() {
@Override
public void run() {
Long now = System.currentTimeMillis();
// 取出小于等于當(dāng)前時(shí)間的元素
Set<Object> set = zset.rangeByScore("AA", 0, now);
if(!set.isEmpty()){
log.info("當(dāng)期時(shí)間:{},過(guò)期數(shù)據(jù)={}", format.format(new Date(now)), set.toArray());
// zset中刪除過(guò)期的元素
zset.removeRangeByScore("AA", 0, now);
}
}
},10,100);// 延時(shí)10毫秒執(zhí)行,每隔100毫秒執(zhí)行一次,1秒執(zhí)行10次做到盡量精準(zhǔn)
運(yùn)行結(jié)果:

最后總結(jié)
使用redis的有序集合zset實(shí)現(xiàn)延遲隊(duì)列,核心就在score分值上,通過(guò)當(dāng)前時(shí)間加上延遲時(shí)間作為score,使用zremrangebyscore 命令取出0到當(dāng)前時(shí)間(long型)的元素,能夠取出來(lái)的就是過(guò)期的元素。當(dāng)然,這個(gè)算法也存在不足之處,在取值的時(shí)候,采用了輪詢,會(huì)白白地浪費(fèi)一部分性能。當(dāng)然我們主要是了解這個(gè)算法是怎么實(shí)現(xiàn)的。
參考文檔:
https://blog.csdn.net/u012791490/article/details/125243933
https://mp.weixin.qq.com/s/WymM9LDjEcNZDhx4e6nLkA
https://zhuanlan.zhihu.com/p/423916169