1. 程式人生 > >管理大量定時任務,如果高效觸發超時?

管理大量定時任務,如果高效觸發超時?

img fff ima app 100% font nbsp back round

1. 背景

很多時候,業務有定時任務或定時超時的需求,當任務量很大時,可能需要維護大量的timer,或者進行低效的掃描。

例如:對每個用戶會維護一個APP到服務器的TCP連接,用來實時收發信息,對這個TCP連接,如果連續30s沒有請求包,服務端就要將這個連接斷開。

一般說怎麽實現這類需求呢?

2. 一般思路

2.1 輪詢掃描法

(1)用一個Map<uid, last_packet_time>來記錄每一個uid最近一次請求時間last_packet_time;

(2)當某個用戶uid有請求包來到,實時更新這個Map;

(3)啟動一個timer,當Map中不為空時,輪詢掃描這個Map,檢查每個uid的last_packet_time是否超過30s,如果超過則進行超時處理

2.2 多timer觸發法

(1)用一個Map<uid, last_packet_time>來記錄每一個uid最近一次請求時間last_packet_time;

(2)當某個用戶uid有請求包來到,實時更新這個Map,並同時對這個uid請求包啟動一個timer,30s之後觸發;

(3)每個uid請求包對應的timer觸發後,檢查Map中,查看這個uid的last_packet_time是否超過30s,如果超過則進行超時處理

輪詢掃描法:只啟動一個timer,但需要輪詢,效率較低

多timer觸發法:不需要輪詢,但每個請求包要啟動一個timer,比較耗資源

特別在同時在線量很大時,很容易CPU100%。

3. 環形隊列法

三個數據結構:

(1)30s超時,就創建一個index從0到30的環形隊列(本質上數組)

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

(3)同時還有一個Map<uid, index>記錄uid落在環上的哪個slot

技術分享圖片

算法:

(1)啟動一個timer,每隔1s,在上述環形隊列中移動一個,0->1->2->3...->29->30->0...

(2)有一個Current Index指針來標識剛檢測過的slot

當有某用戶uid有請求包達到時:

(1)從Map結構中,查找出這個uid存儲在哪個slot裏

(2)從這個slot的Set結構中,刪除這個uid

(3)將uid重新加入到新的slot,具體是哪一個slot呢?-->Current Index指針所指向的上一個slot,因為整個slot,會被timer在30s之後掃描到

(4)更新Map,這個uid對應slot的index值

哪些元素會被超時刪除掉?

Current Index每秒移動一個slot,這個slot對應的Set<uid>中所有uid都應該被集體超時,如果最近30s有請求包來到,一定被放到Current Index的前一個slot,Current Index所在的slot對應Set中所有元素,都是最近30s沒有請求包來到的。

所以,當沒有超時時,Current Index掃描到的每一個Slot的Set中應該都沒有元素。

這個環形隊列法是一個通用的方法,Set和Map中可以使任何task,本文的uid是一個最簡單的舉例。

4. Netty - HashedWheelTimer

管理大量定時任務,如果高效觸發超時?