内存延迟队列
内存延迟队列一般能容纳的数据比较少,消息的数量很容易受到内存和cpu的限制。比较适合用在单机系统中。消息的数量一般在几万,几十万级别。
小顶堆
见下图。生产者不断往小顶堆中放入消息。放入消息时取出消息的到期时间跟堆顶消息的到期时间比较。如果比堆顶元素还小,就唤醒消费者线程,让消费者线程重新设定等待时间。
消费者每次只需要取出堆顶的时间戳,跟当前的时间戳比较,然后执行或者睡眠等待即可。
时间轮算法
见图
一般采用定时器+哈希桶+链表的方式。
分布式
redis延迟队列
对于每个消息,当前的时间戳是ts,需要延迟的时间是d,那么就在ZSET中加入一条记录:ZADD ts+d msg_id,然后将msg_id加入到redis中的哈希表中。客户端按照允许的时间精度,死循环读取ZSET中的数据ZRANGEBYSCORE delay_queue 0, current_timestamp,得到本次需要延迟发送的消息id。然后再根据msgID取redis中的哈希表中取出消息发送。完毕后把ZSET和HASH表中的数据都删除即可。
加入消息很多,单台redis不够存放,可以考虑将消息体放在跟ZSET不同的redis服务器上。
按照一条消息1KB来算,一台20G的redis,可以容纳百万级别的消息。
这个方案的缺点还在于
1.数据无法持久化。
2.如果定时的消息非常多,而且间隔大,很容易造成消息堆积导致redis内存爆炸,导致数据丢失
整个系统的架构图如下:
redis+mysql延迟队列
为了改进上面方案的缺点,我们可以将HASH表中消息体的内容放在mysql中,分表存放,单表可以存放多至上千万条,分个100张表可以容纳10亿级别的消息。redis则单纯用来做排序工具。
如果单redis客户端消费速度不够快,可以考虑将过期时间切分为多个ZSET集合。
比如60秒为一个有序集合,15分钟为一个有序集合。等等,然后针对每个集合,使用单台机器来消费。