【轉】基於環形隊列法的延遲消息隊列設計2(58沈劍)

不少時候,業務有「在一段時間以後,完成一個工做任務」的需求。數組

 

例如:滴滴打車訂單完成後,若是用戶一直不評價,48小時後會將自動評價爲5星。數據結構

 

通常來講怎麼實現這類「48小時後自動評價爲5星」需求呢?線程

 

常見方案:啓動一個cron定時任務,每小時跑一次,將完成時間超過48小時的訂單取出,置爲5星,並把評價狀態置爲已評價。設計

假設訂單表的結構爲:t_order(oid, finish_time, stars, status, …),更具體的,定時任務每隔一個小時會這麼作一次:3d

select oid from t_order where finish_time > 48hours and status=0;指針

update t_order set stars=5 and status=1 where oid in[…];blog

若是數據量很大,須要分頁查詢,分頁update,這將會是一個for循環。隊列

 

方案的不足:ci

(1)輪詢效率比較低消息隊列

(2)每次掃庫,已經被執行過記錄,仍然會被掃描(只是不會出如今結果集中),有重複計算的嫌疑

(3)時效性不夠好,若是每小時輪詢一次,最差的狀況下,時間偏差會達到1小時

(4)若是經過增長cron輪詢頻率來減小(3)中的時間偏差,(1)中輪詢低效和(2)中重複計算的問題會進一步凸顯

 

如何利用「延時消息」,對於每一個任務只觸發一次,保證效率的同時保證明時性,是今天要討論的問題。

 

2、高效延時消息設計與實現

高效延時消息,包含兩個重要的數據結構:

(1)環形隊列,例如能夠建立一個包含3600個slot的環形隊列(本質是個數組)

(2)任務集合,環上每個slot是一個Set<Task>

 

同時,啓動一個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<Task>中

(2)計算這個Task的Cycle-Num,因爲環形隊列是3600格(每秒移動一格,正好1小時),這個任務是3610秒後執行,因此應該繞3610/3600=1圈以後再執行,因而Cycle-Num=1

 

Current Index不停的移動,每秒移動到一個新slot,這個slot中對應的Set<Task>,每一個Task看Cycle-Num是否是0:

(1)若是不是0,說明還須要多移動幾圈,將Cycle-Num減1

(2)若是是0,說明立刻要執行這個Task了,取出Task-Funciton執行(能夠用單獨的線程來執行Task),並把這個Task從Set<Task>中刪除

 

使用了「延時消息」方案以後,「訂單48小時後關閉評價」的需求,只需將在訂單關閉時,觸發一個48小時以後的延時消息便可:

(1)無需再輪詢所有訂單,效率高

(2)一個訂單,任務只執行一次

(3)時效性好,精確到秒(控制timer移動頻率能夠控制精度)

 

3、總結

環形隊列是一個實現「延時消息」的好方法,開源的MQ好像都不支持延遲消息,不妨本身實現一個簡易的「延時消息隊列」,能解決不少業務問題,並減小不少低效掃庫的cron任務。

 

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

相關文章
相關標籤/搜索