如果遇到如下场景:
- 用户下单后,24小时内未付款则自动取消订单;
- 用户设定5分钟后提醒通知用户。
我们就需要延时消息队列,它是这样的一个消息队列:进入队列的消息在延时时间到达后才能出列被消费,时间未到达时对消费者是不可见的。
以下介绍的几种实现方案,可以依据具体业务需求进行选择。
一、简单粗暴的MongoDB
MongoDB吃内存的特性一值备受人诟病,但也是内存的大量使用,使得MongoDB比MySQL性能更好。使用MongoDB的文档原子修改特性,可以快速实现延时消息队列功能,在业务初期,不啻为简单粗暴的选择。
入列:
db.queue.insert({"body":"Hello","effective_at":ISODate("2017-09-17T00:30:00Z"),"ack":0,"read_count":0});
轮询出列(轮询间隔取决于业务需要的时间精度):
db.queue.findAndModify({
query:{read_count:{$eq:0},effective_at:{$lte:ISODate("2017-09-17T00:30:00Z")}},
update:{
$inc:{read_count:1},
}
})
{
"_id" : ObjectId("6049cd622764d56dec638f03"),
"body" : "Hello",
"effective_at" : ISODate("2017-09-17T00:30:00Z"),
"ack" : 0,
"read_count" : 0
}
消费确认:
db.queue.update(
{"_id":ObjectId("6049cd622764d56dec638f03")},
{$set:{"ack":1}}
)
非常适合进行定时或延时的内容推送、通知提醒等场景。
二、重量级、高可用的RabbitMQ
如果你的数据一致性比较重要,例如处理退款等重要业务逻辑,可以考虑适用RabbitMQ,下面介绍RabbitMQ如何实现延时消息队列。
三、高性能的Redis
redis之所以快是因为它是内存数据库,意味着数据可能会丢失,但不用过多担心,简单的主从备份即可满足大部分生产环境对数据可靠性的要求。遗憾的是使用redis的基本数据没法实现异步延时消息队列的,有人会说使用redis的有序集合不就可以了吗?如果每秒只有一个任务,当然没问题。倘若并发场景下,你可以控制每次取出来的消息数量吗?你可以保证消息被消费失败后能重回队列吗?纯粹地使用有序集合是没法满足这样的需求的。
其实我们可以使用redis的lua脚本来实现延时队列。
注意点:在redis集群中注意hashtag的使用。
使用lua脚本时,可以使用lua脚本的hash。
四、自己动手写一个
自己动手,丰衣足食。我们还可以实现到期主动通知的功能。 主要思路是: 消息队列服务端与消费者维持长连接,当有消息到期需要出列时,通知客户端。
下面提供了一个简单的demo,仅供参考。
//