MQ如何實現訊息延時

2021-08-30 19:29:10 字數 1824 閱讀 1385

很多時候,業務有「在一段時間之後,完成乙個工作任務」的需求。

例如:滴滴打車訂單完成後,如果使用者一直不評價,48小時後會將自動評價為5星。

一般來說怎麼實現這類「48小時後自動評價為5星」需求呢?

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

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

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

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

如果資料量很大,需要分頁查詢,分頁update,這將會是乙個for迴圈。

方案的不足:

(1)輪詢效率比較低

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

(3)時效性不夠好,如果每小時輪詢一次,最差的情況下,時間誤差會達到1小時

(4)如果通過增加cron輪詢頻率來減少(3)中的時間誤差,(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 和 延時模組 區分開來,延時訊息模組是乙個獨立的服務 程序。延時訊息先保留到其他儲存介質中,然後在訊息到期時再投遞到 mq。1.1 基於 資料庫 如mysql 基於關係型資料庫 如mysql 延時訊息表的方式來實現。create table delay...

實現mq延時佇列(訂單延時取消)

簡單實現mq延時佇列 1.rabbitmqconfiguration mq配置類 configuration slf4j public class rabbitmqconfiguration string host,value int port,value string username,value...

Redis 實現訊息佇列 MQ

redis 2.4版本之後就內建佇列的功能了,如果是日常比較簡單的佇列應用,可以選擇redis 效率還很高的!redis 還能實現有序和無序兩種佇列 只討論生產者和消費者這種模式的佇列 一 有序佇列 1 生產者 redis new redis redis pconnect 127.0.0.1 637...