你真的瞭解延時佇列嗎(一)

咖啡拿鐵發表於2018-07-30

1 使用場景

  • 關閉空閒連線。伺服器中,有很多客戶端的連線,空閒一段時間之後需要關閉之。
  • 清理過期資料業務上。比如快取中的物件,超過了空閒時間,需要從快取中移出。
  • 任務超時處理。在網路協議滑動視窗請求應答式互動時,處理超時未響應的請求。
  • 下單之後如果三十分鐘之內沒有付款就自動取消訂單。
  • 訂餐通知:下單成功後60s之後給使用者傳送簡訊通知。
  • 當訂單一直處於未支付狀態時,如何及時的關閉訂單,並退還庫存?
  • 如何定期檢查處於退款狀態的訂單是否已經退款成功?
  • 新建立店鋪,N天內沒有上傳商品,系統如何知道該資訊,併傳送啟用簡訊?
  • 定時任務排程:使用DelayQueue儲存當天將會執行的任務和執行時間,一旦從DelayQueue中獲取到任務就開始執行。

2 延時任務-實現方式

  • 定期輪詢(資料庫等)
  • DelayQueue
  • Timer
  • ScheduledExecutorService
  • 時間輪(kafka)
  • RabbitMQ
  • Quartz
  • Redis Zset
  • Koala
  • JCronTab
  • SchedulerX(阿里)
  • 有贊延遲佇列

2.1 輪詢

特點:定期輪訓資料庫,設定狀態。

優點:實現簡單
缺點:資料量過大時會消耗太多的IO資源,效率太低
複製程式碼

2.2 DelayQueue

特點: 無界、延遲、阻塞佇列

a、BlockingQueue+PriorityQueue(堆排序)+Delayed
b、DelayQueue中存放的物件需要實現compareTo()方法和getDelay()方法。
c、getDelay方法返回該元素距離失效還剩餘的時間,當<=0時元素就失效了,
就可以從佇列中獲取到。
複製程式碼

你真的瞭解延時佇列嗎(一)

你真的瞭解延時佇列嗎(一)

這裡為什麼要用leader/follower模式?

  • 如果不是隊首節點,根本不需要喚醒操作!
  • 假設取值時,延時時間還沒有到,那麼需要等待,但這個時候,佇列中新加入了一個延時更短的,並放在了隊首,那麼 此時,for迴圈由開始了,取得是新加入的元素,那之前的等待就白等了,明顯可以早點退出等待!
  • 還有就是如果好多執行緒都在此等待,如果時間到了,同時好多執行緒會充等待佇列進入鎖池中,去競爭鎖資源,但結果只能是一個成功, 多了寫無畏的競爭!(多次的等待和喚醒)
    你真的瞭解延時佇列嗎(一)

2.3 Timer與TimerTask

你真的瞭解延時佇列嗎(一)

  • TaskQueue中的排序是對TimerTask中的下一次執行時間進行堆排序,每次去取陣列第一個。
  • 而delayQueue是對queue中的元素的getDelay()結果進行排序

Timer是一種定時器工具,用來在一個後臺執行緒計劃執行指定任務。它可以計劃執行一個任務一次或反覆多次。 主要方法:

你真的瞭解延時佇列嗎(一)

你真的瞭解延時佇列嗎(一)

2.4 時間輪(kafka)

時間輪名詞解釋:

  • 時間格:環形結構中用於存放延遲任務的區塊;
  • 指標(CurrentTime):指向當前操作的時間格,代表當前時間
  • 格數(ticksPerWheel):為時間輪中時間格的個數
  • 間隔(tickDuration):每個時間格之間的間隔
  • 總間隔(interval):當前時間輪總間隔,也就是等於ticksPerWheel*tickDuration

你真的瞭解延時佇列嗎(一)

你真的瞭解延時佇列嗎(一)

根據每個TimerTaskEntry的過期時間和當前時間輪的時間,選擇一個合適的bucket(實際上就是TimerTaskList),把這個TimerTaskEntry物件放進去,同時如果bucket的過期時間有更新,就將這個bucket推進DelayQueue,重新排序

例子:假設編號為0的時間格或者桶儲存著到期時間為t,每一個tick的持續時間(tickDuration)為20ms,在這個格子裡只能儲存著到期時間為[t~t+20]ms的任務,假設時間輪的時間格有n個,每一個間隔1ms,到期時間為m(ms),那麼計算公式m%n = 所在的時間格或者桶,比如n=10,m=34ms,那麼他所在桶或者時間格是4

2.5 RabbitMQ-延時任務

RabbitMQ本身沒有直接支援延遲佇列功能,但是可以通過以下特性模擬出延遲佇列的功能。

RabbitMQ可以針對Queue和Message設定 x-message-tt,來控制訊息的生存時間,如果超時,則訊息變為dead letter RabbitMQ針對佇列中的訊息過期時間有兩種方法可以設定。 A: 通過佇列屬性設定,佇列中所有訊息都有相同的過期時間。 B: 對訊息進行單獨設定,每條訊息TTL可以不同。

你真的瞭解延時佇列嗎(一)

2.6 Quartz

你真的瞭解延時佇列嗎(一)

為什麼不用Timer?

  • Timers沒有持久化機制.
  • Timers不靈活 (只可以設定開始時間和重複間隔,不是基於時間、日期、天等(秒、分、時)的)
  • Timers 不能利用執行緒池,一個timer一個執行緒
  • Timers沒有真正的管理計劃

核心概念:排程器、任務和觸發器。

三者關係:排程器負責排程各個任務,到了某個時刻或者過了一定時間,觸發器觸動了,特定任務便啟動執行。

你真的瞭解延時佇列嗎(一)

  1. scheduler是一個計劃排程器容器(總部),容器裡面可以盛放眾多的JobDetail和trigger,當容器啟動後,裡面的每個JobDetail都會根據trigger按部就班自動去執行。
  2. JobDetail是一個可執行的工作,它本身是有狀態的。
  3. Trigger代表什麼時候去調。
  4. 當JobDetail和Trigger在scheduler容器上註冊後,形成了裝配好的作業(JobDetail和Trigger所組成的一對兒),就可以伴隨容器啟動而排程執行了。
  5. scheduler是個容器,容器中有一個執行緒池,用來並行排程執行每個作業,這樣可以提高容器效率。

待續。。。

如果上面問題有什麼疑問的話可以關注公眾號,來和我一起討論吧,關注即可免費領取海量最新java學習資料視訊,以及最新面試資料。

如果大家覺得這篇文章對你有幫助,或者你有什麼疑問想提供1v1免費vip服務,都可以關注我的公眾號,關注即可免費領取海量最新java學習資料視訊,以及最新面試資料,你的關注和轉發是對我最大的支援,O(∩_∩)O:

你真的瞭解延時佇列嗎(一)

相關文章