訂單超時自動關閉的實現方案總結

周小元_zcy發表於2020-09-24

統一來說,業務有“在一段時間之後,完成一個工作任務”的需求。
實現這種定時任務有哪些方法呢,來總結一下想到的方法。
一、定時輪詢
這是一個比較直接的思路,啟動一個計劃任務,每隔一定時間處理一次,這種處理方式只是適用比較小而簡單的專案。
假設訂單表的結構為:t_order(oid, finish_time, stars, status, …),更具體的,定時任務每隔一個小時會這麼做一次:
select oid from t_order where finish_time > 30minite and status=0;
update t_order set stars=5 and status=1 where oid in[…];
如果資料量很大,需要分頁查詢,分頁update,這將會是一個for迴圈。

定時輪詢的不足:
1、時效性差,會有一定的延遲,這個延遲時間最大就是每隔一定時間的大小,如果你設定每分鐘定時輪詢一次,那麼理論上訂單取消時間的最大誤差就有一分鐘,當然也可能更大,比如一分鐘之內有大量資料,但是一分鐘沒處理完,那麼下一分鐘的就會順延。
2、效率低。

二、被動取消
被動取消的方式很簡單:只有當使用者查詢訂單資訊時,我們再判斷該訂單是否超時,如果超時再進行超時邏輯的處理。
但是這種方式依賴於使用者的查詢操作觸發,這也就是說如果使用者不進行查詢訂單的操作,該訂單就永遠不會被取消。
不足:
1、會產生額外影響
比如統計,訂單數量等產生影響
2、影響使用者體驗
使用者開啟訂單列表可能要處理大量資料,影響顯示的實時性。

三、延時訊息
延時訊息設計與實現
高效延時訊息,包含兩個重要的資料結構:
(1)環形佇列,例如可以建立一個包含3600個slot的環形佇列(本質是個陣列)
(2)任務集合,環上每一個slot是一個Set
在這裡插入圖片描述

同時,啟動一個timer,這個timer每隔1s,在上述環形佇列中移動一格,有一個Current Index指標來標識正在檢測的slot。

Task結構中有兩個很重要的屬性:
(1)Cycle-Num:當Current Index第幾圈掃描到這個Slot時,執行任務
(2)Task-Function:需要執行的任務指標

假設當前Current Index指向第一格,當有延時訊息到達之後,例如希望3610秒之後,觸發一個延時訊息任務,只需:
(1)計算這個Task應該放在哪一個slot,現在指向1,3610秒之後,應該是第11格,所以這個Task應該放在第11個slot的Set中
(2)計算這個Task的Cycle-Num,由於環形佇列是3600格(每秒移動一格,正好1小時),這個任務是3610秒後執行,所以應該繞3610/3600=1圈之後再執行,於是Cycle-Num=1

Current Index不停的移動,每秒移動到一個新slot,這個slot中對應的Set,每個Task看Cycle-Num是不是0:
(1)如果不是0,說明還需要多移動幾圈,將Cycle-Num減1
(2)如果是0,說明馬上要執行這個Task了,取出Task-Funciton執行(可以用單獨的執行緒來執行Task),並把這個Task從Set中刪除

使用了“延時訊息”方案之後,“訂單48小時後關閉評價”的需求,只需將在訂單關閉時,觸發一個48小時之後的延時訊息即可:
(1)無需再輪詢全部訂單,效率高
(2)一個訂單,任務只執行一次
(3)時效性好,精確到秒(控制timer移動頻率可以控制精度)

四、總結
環形佇列是一個實現“延時訊息”的好方法,開源的MQ好像都不支援延遲訊息,不妨自己實現一個簡易的“延時訊息佇列”,能解決很多業務問題,並減少很多低效掃庫的cron任務。

另外,關於MQ的可達性、冪等性未來撰文另述。

相關文章