延時任務(wù)實(shí)現(xiàn)方案
點(diǎn)擊下方“IT牧場”,選擇“設(shè)為星標(biāo)”

業(yè)務(wù)場景
我們買火車票或者叫外賣的時候,下完單之后會跳轉(zhuǎn)到支付頁面,頁面里通常會有一個計時器,要求在指定時間內(nèi)完成支付,否則訂單就會被自動取消。這就是延時任務(wù)的一個典型業(yè)務(wù)場景。分析這個場景,其實(shí)最關(guān)鍵的就是如何在訂單超時的時候立即觸發(fā)取消訂單的動作。
那么如何實(shí)現(xiàn)這種延時業(yè)務(wù)呢?通常有以下4種方案。
定時任務(wù)輪詢db
用戶下單后db中會生成一條訂單記錄,記錄了訂單號、用戶ID、創(chuàng)建時間、訂單詳情、訂單狀態(tài)等信息。假設(shè)超時時間是600秒,我們后臺起一個定時任務(wù),每隔固定時間運(yùn)行一次,每次掃描db中的超時訂單select * from order where createTime <= now()-600,然后取消查詢到的訂單。
這種方法實(shí)現(xiàn)簡單,但是有很多缺點(diǎn)。超時時間通常是秒級的,如果定時任務(wù)每秒運(yùn)行一次,那么就相當(dāng)于每秒就要對訂單表做一次掃描,這是相當(dāng)消耗db資源的操作,因此定時任務(wù)一般不會設(shè)置為秒級;但是如果設(shè)置為分鐘級,又會犧牲即時性,比如600秒超時,很有可能660秒的時候訂單才被取消。
DelayQueue
JDK的DelayQueue(延遲隊(duì)列)是無界阻塞隊(duì)列,只有在延遲期滿時才能從中獲取元素。每生成一個訂單,在把訂單記錄到db的同時,要把訂單id等信息投遞到延遲隊(duì)列中去,隊(duì)列會按照超時時間進(jìn)行排序,最先超時的訂單排在隊(duì)列的頭部;起一個單獨(dú)的線程不斷地從隊(duì)列中摘取元素然后去做取消訂單的動作。
這種方法最大的缺點(diǎn)就是沒有將超時信息持久化,服務(wù)重啟之后延遲隊(duì)列的元素不會被恢復(fù)。
redis的zset
在redis中創(chuàng)建一個key是”delayOrders”的zset,每個member就是訂單ID,member的score就是該訂單的超時時間戳。我們每次從zset中取出score最小也就是最先超時的元素,判斷其是否超時,如果超時就將其從zset中刪除并取消訂單,如果未超時就繼續(xù)執(zhí)行下一次循環(huán)。
RabbitMQ的TTL+DLX
RabbitMQ可設(shè)置消息過期時間(TTL),當(dāng)消息過期后可以將該消息投遞到隊(duì)列上設(shè)置的死信交換器(DLX)上。然后投遞到死信隊(duì)列中,重新消費(fèi)。

四種方案對比
| 方案 | 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|---|
| 定時任務(wù)輪詢db | 實(shí)現(xiàn)簡單、無技術(shù)難點(diǎn)、異常恢復(fù)、支持分布式/集群環(huán)境 | 影響數(shù)據(jù)庫性能、時效性差 |
| DelayQueue | 實(shí)現(xiàn)簡單、性能較好 | 無法異常恢復(fù)、分布式/集群實(shí)現(xiàn)困難 |
| redis的zset | 解耦、異常恢復(fù)、擴(kuò)展性強(qiáng)、支持分布式/集群環(huán)境 | 增加redis維護(hù)、占用帶寬 |
| RabbitMQ的TTL+DLX | 解耦、異常恢復(fù)、擴(kuò)展性強(qiáng)、支持分布式/集群環(huán)境 | 增加RabbitMQ維護(hù)、占用帶寬 |
source: //xiangxianzui.github.io/2020/02/延時任務(wù)實(shí)現(xiàn)方案
干貨分享
最近將個人學(xué)習(xí)筆記整理成冊,使用PDF分享。關(guān)注我,回復(fù)如下代碼,即可獲得百度盤地址,無套路領(lǐng)取!
?001:《Java并發(fā)與高并發(fā)解決方案》學(xué)習(xí)筆記;?002:《深入JVM內(nèi)核——原理、診斷與優(yōu)化》學(xué)習(xí)筆記;?003:《Java面試寶典》?004:《Docker開源書》?005:《Kubernetes開源書》?006:《DDD速成(領(lǐng)域驅(qū)動設(shè)計速成)》?007:全部?008:加技術(shù)群討論
加個關(guān)注不迷路
喜歡就點(diǎn)個"在看"唄^_^
