版權聲明:本文爲博主原創文章,未經博主允許不得轉載。轉載請標明出處:http://blog.csdn.net/ligang2585116!
圓通處事,方能達到目的!
開發中我們經常會遇到一些需要定時來解決的業務場景。比如,有這樣一個需求:“如果連續30s沒有請求包(例如登錄,消息,keepalive包),服務端就要將這個用戶的狀態置爲離線”。
輪詢處理
將所有任務都添加到某集合中,定時輪詢掃描,如果達到條件則進行相關處理;
let map = new Map();
function doAction(uid) {
map.set(uid, new Date().getTime());
}
setInterval(function(){
for(let uid of map.keys()) {
if(+new Date() - map.get(uid) > 30000) {
map.delete(uid);
console.log(`${uid}超過30s未做任何操作,設置爲離線!`);
}
}
}, 10000);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
方案的不足:
- 效率低下。已經被執行過記錄,仍然會被掃描(只是不會出現在結果集中),存在大量的重複計算;
- 時效性差。時間誤差取決於輪詢的間隔;如果間隔過小,重複被掃描的次數更高,效率會變得更低下。
定時處理
每來一個任務,啓動一個定時器,達到定時器時間,執行相關處理;
function doAction(uid) {
map.set(uid, new Date().getTime());
setTimeout(function() {
console.log(`${uid}超過30s未做任何操作,設置爲離線!`);
}, 30000);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
方案的不足:
- 定時數過多,導致內存使用率過高,容易導致崩潰。
環形隊列處理
數據結構:
- 環形隊列ListLoop,例如可以創建一個包含0-30的slot**環形隊列**(本質是個數組);
- 每個環上的任務集合Slot,環上每一個slot是一個Set;
- 記錄每個Task對應落到Slot的Map集合;
執行過程:
第一步:啓動一個timer,每隔1s,在上述環形隊列中移動一格,0->1->2->3…->29->30->0…
有一個CurrentSlotIndex指針來標識剛檢測過的slot ;
第二步:當有某用戶uid有請求包到達時,從Map結構中,查找出這個uid存儲在哪一個slot裏;
第三步:如果存在,從這個slot的Set結構中,刪除這個uid,否則跳過該步驟;
第四步:將uid重新加入到新的slot中(CurrentSlotIndex指針所指向的上一個slot)因爲這個slot,會被timer在30s之後掃描到
第五步:更新Map,重新設置該uid對應slot的index值
// new Array(31).fill(new Set())
// No,數組中所有Set集合爲同一個
let listLoop = new Array(31),
map = new Map(), // 記錄每個uid的slotIndex
currentSlotIndex = 1; // 當前要檢測的slot
function doAction(uid) {
// 如果循環隊列中已存在該uid,需要先幹掉,重新計時
let slotIndex = map.get(uid);
slotIndex && listLoop[slotIndex].delete(uid);
// 將該uid重現添加到循環隊列中
// 週期31,新插入的置入當前的後一個(即,30s後可以掃描到它)
// 更新map中這個uid的最新slotIndex
slotIndex = currentSlotIndex - 1;
listLoop[slotIndex] = listLoop[slotIndex] ?
listLoop[slotIndex].add(uid) : new Set().add(uid);
map.set(uid, slotIndex);
}
// 每秒鐘移動一個slot,這個slot對應的set集合中所有uid都爲超時
// 如果所有slot對應的set集合都爲空,則表示沒有uid超時
setInterval(function() {
var slotSet = listLoop[currentSlotIndex];
if(slotSet && slotSet.size > 0) {
for(let uid of slotSet.values()) {
// 執行完的uid從map集合中剔除
map.delete(uid);
console.log(`<${uid}>超過30s未做任何操作,設置爲離線!`);
}
// 置空該集合
slotSet.clear();
}
// 指標繼續+1
currentSlotIndex = (++currentSlotIndex) % 31;
}, 1000);
// 思路、注意Map集合的內心移除情況。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
方案的優點:
- 無需再輪詢全部訂單,效率高
- 無重複執行,一個訂單,任務只執行一次
- 效性好,精確到秒(控制timer移動頻率可以控制精度)
參照文章:10w定時任務,如何高效觸發超時、1分鐘實現“延遲消息”功能
舉一反三
上述展示描述了一種業務場景,通過環形隊列的方式我們還可以處理很多類似場景。
- 某打車軟件訂單完成後,如果用戶一直不評價,48小時後會將自動評價爲5星;
- 某數據產品用戶修改設置,1小時後生效;
- …