1.1、JDK延遲隊列
該方案是利用JDK自帶的java.util.concurrent包中的DelayQueue隊列。
public class DelayQueue<E extends Delayed>extends AbstractQueue<E> implements BlockingQueue<E>
這是一個無界阻塞隊列,該隊列只有在延遲期滿的時候才能從中獲取原始,放入DelayQueue中的對象,必須實現Delayed接口
優點:
1.效率高
缺點:
1.JVM重啟,數據會全部丟失
2.可擴展性難度高
3.可能出現內存溢出異常
4.內部很多東西可能需要開發人員手動編寫,很多東西沒有封裝
1.2、定時任務
這種方式最簡單,啟動一個計划任務,每隔一定時間(假設1分鍾)去掃描一次數據庫,通過訂單時間來判斷是否超時,然后進行UPDATE或DELETE操作
優點:
1.實現簡單
2.高可用,支持集群(Quartz\TBSchedule\XX-JOB\Elastic-Job\Staurm\LTS等)
缺點:
1.服務器內存消耗大
2.存在延遲,比如每一份掃描一次,延遲就是1分鍾。也可能更久,比如1分鍾之內有大量數據,1分鍾沒處理完,那么下一分鍾就會順延
3.效率低
4.數據庫壓力大,訂單數據過大時,數據庫壓力也會增加
1.3被動取消
利用懶加載的思想,當用戶或商戶查詢訂單時,再判斷該訂單是否超時,超時則進行業務處理。
這種方式依賴於用戶的查詢操作觸發,如果用戶不進行查詢訂單操作,該訂單就永遠不會被取消。所以,實際應用中,也是"被動取消+定時任務"的組合方式來實現。這種情況下定時任務的時間可以設置的稍微‘長’一點
優點:
1.實現簡單
2.支持集群(Quartz\TBSchedule\XX-JOB\Elastic-Job\Staurm\LTS等)
缺點:
1.產生額外影響,比如統計訂單、統計庫存等
2.影響用戶體驗,打開訂單列表時需要處理數據,從而降低顯示的實時性
1.4Redis Sorted Set
Redis有序集合(Sorted Set)每個元素都會關聯一個double類型的分數score。Redis可以通過分數來為集合中的成員進行從小到大的排序。
該方案可以將訂單超時時間戳與訂單編號分別設置為score和member。系統掃描第一個元素判斷是否超時,超時則進行業務處理。
然而,這一版本存在一個致命的硬傷,在高並發條件下,多個消費者會取到同一個訂單編號,又需要編寫Lua腳本保證原子性或使用分布式鎖,用了分布式鎖性能又下降了。
優點:
1.可靠性,基於Redis自身的持久化性實現消息持久化
2.高可用性,支持單價、主從、哨兵、集群多種模式
缺點:
1.單個有序集合無法支持太大的數量
2.需要額外進行Redis的維護
1.5、Redis事件通知
改方案是使用Redis的Keyspace Notifications,利用該機制可以在Key失效后,提供一個回調,實際上就是Redis會給客戶端發送一個消息。需要Redis版本2.8以上。
優點:
1.可靠性,基於Redis自身的持久化特性實現消息持久化
2.高可用性,支持單擊、主從、哨兵、集群多種模式
缺點:
1.開啟鍵通知會對Redis產生額外的開銷
2.目前鍵通知功能Redis並不保證消息必達,Redus客戶端斷開連接所以key會丟失
3.需要額外進行Redis的維護
1.6、時間輪算法
時間輪算法可以類比於時鍾,按某一個方向固定頻率輪動,每一個跳動稱為一個tick。這樣看出時間輪有3個重要的屬性參數:
-
ticksPerWheel:一輪tick數
-
tickDuration:一個tick的持續時間
-
timeUnit:時間單位
當ticksPerWheel=60,tickDuration=1,timeUnit=秒,這就和現實中的時鍾走動完全類型。
優點:
-
效率高
-
如果使用Netty的HashedWheelTimer來實現,代碼復雜比JDK的DelayQueue低
-
如果使用第三方中間件來實現,支持集群擴展,高吞吐量、消息持久化等。
缺點:
-
服務器重啟后,數據全部丟失,怕宕機
-
集群擴展麻煩,難度較高
-
由於內存條件限制的原因,下單未付款的訂單過多時,容易出現OOM異常
-
如果使用第三方中間件實現,需要額外進行第三方中間件的維護
-
1.7、RabbitMQ
1.7.1、延遲隊列
隊列是一種特殊的線性表,特殊之處在於它只允許在表的前端(front)進行刪除操作,而在表的后端(rear)進行插入操作,和棧一樣,隊列是一種操作受限制的線性表。進行插入操作的端為隊尾,進行刪除操作的端稱為隊頭。
延遲隊列,最重要的特性就體現在它的延時屬性上,跟普通隊列不一樣的是,普通隊列中的元素總是等着希望被早點取出消費,而延遲隊列中的元素則是希望在指定時間被取出消費,所以延遲隊列中的元素是都是帶時間屬性的。
簡單來說,延遲隊列就是用來存放需要在指定時間被處理的元素的隊列。
本文使用RabbitiMQ也是通過延遲隊列的機制來實現訂單超時的處理。然而RabbitMg自身並沒有延遲隊列這個功能,實現該功能一般有以下兩種方式:
-
利用TTL(Time To Live)和DLX(Dead Letter Exchanges)實現延遲隊列
-
利用RabbitMQ的社區插件rabbitmq_delayed_message_exchange 實現
優點:
-
可靠性,消息持久化
-
高可用,非常方便部署負載均衡,實現高可用和吞吐量,輕松聯合多個可用性區域和塊
-
易管理和監控,使用HTTP-API,命令行工具或其他UI工具來管理和監控RabbitMQ
缺點:
-
系統可用性降低
-
系統復雜性變高
-
系統一致性問題
-
總結:
當然也要分實際情況來決定,如果貴司已經在用RabbitMg的情況下,延遲任務肯定首選使用RabbitMQ來實現,如果貴司並沒有使用RabbitMQ,就為了實現這樣一個功能而強行使用RabbitMg,在一個穩定運行的系統中引入一個第三方中間件是需要考慮很多問題的,否則就會得不償失。
目前大型互聯網公司多多少少都會引入消息中間件,畢竟它擁有解耦、異步、流量削峰、日志處理等優點及功能,是分布式系統中重要的組件。在這種情況下,使用消息中間件來實現延遲任務就變得理所當然了。
P:在實際項目中,還是要根據不同的業務需求以及各種方案的優缺點進行選擇合適的方案,並不是性能最強就是最合適的。