需求背景
- 用户下订单成功之后隔20分钟给用户发送上门服务通知短信
- 订单完成一个小时之后通知用户对上门服务进行评价
-
业务执行失败之后隔10分钟重试一次
类似的场景比较多 简单的处理方式就是使用定时任务 假如数据比较多的时候 有的数据可能延迟比较严重,而且越来越多的定时业务导致任务调度很繁琐不好管理。
序号 | 方案 | 实现原理 | 优点 | 缺点 |
---|---|---|---|---|
1 | java DelayQueue | wait-notify机制 | 不引入其他服务依赖,wait-notify机制,不做polling,不会浪费cpu。 | 数据保存在JVM内存中,当应用重启会造成数据丢失,或者数据量大时造成DelayQueue过大。 |
2 | RabbitMQ 死信队列 | 利用ttl 以及DLE (Dead Letter Exchanges)可以模拟出延迟队列 | 开源,现成的稳定的实现方案 | 每个延迟时间需要单独一个队列(5分钟延时是一个,10分钟延迟是另外一个)。除此以外,使用较复杂,对开发者有一定的要求。 |
3 | redis zset | 使用ZSET做优先队列,按照Score维持优先级 设定轮询,每分钟轮询一次zset,找出score小于当前秒数的数据,进行处理,然后将key在zset内删除 | 比较简单,支持持久化 | 比较简陋的延时队列,有意外报错的话,大不了就多轮询几次。或者等人发现了再解决,也没有ack机制,所以也是挺不靠谱的,使用在不太重要的地方还行 |
4 | 自研DelayQueue |
执行过程:
参数说明:
- Topic:一组相同类型Job的集合(队列)。供消费者来订阅。
- Id:Job的唯一标识。用来检索和删除指定的Job信息。
- Delay:Job需要延迟的时间。单位:秒。(服务端会将其转换为绝对时间)
- Body:Job的内容,供消费者做具体的业务处理,以json格式存储。
- callBack:回调接口地址。使用http协议,该地址是一个url。
存储结构
名称 | redis类型 | 保存数据格式 | delayJob状态 | FailedDelayJob状态 |
---|---|---|---|---|
DelayQ | zset | job--->score | published | N/A |
ReadyQ | hash table | jobId--->job | ready | ready |
FailedDelayQ | zset | job--->score | N/A | published |
状态转换说明:
1. 客户端publish一个job,首先会被保存到delayQ中,此时状态为published(DelayJob);
2. 超时后,会从delayQ中取出,放入ReadyQ中,状态为Ready(DelayJob);
3 回调成功,则删除readyQ状态是deleted(DelayJob);
4 回调失败,DelayedJob 变为FailedDelayJob(transfer)删除readyQ信息状态为published(FailedDelayJob);
5.FailedDelayJob超时,会从FailedDelayQ删除,放入ReadyQ,状态为Ready(FailedDelayJob);
6.回调成功,则删除readyQ,状态是deleted(FailedDelayJob);
7.回调失败,小于重试次数,则再次transfer,删除readyQ信息,状态为published(FailedDelayJob);
8.回调失败,超过重试次数,则归档,发提示短信,同时删除readyQ&数据字典信息,deleted(FailedDelayJob)。
DelayJob和FailedDelayJob转换: