定時任務高效觸發


和架構師》      IBM PowerAI人工智能馬拉

定時任務高效觸發

標籤: 算法定時任務高效觸發JavaScriptjs定時
151人閱讀 評論(0) 收藏 舉報
本文章已收錄於:
分類:

圓通處事,方能達到目的!

開發中我們經常會遇到一些需要定時來解決的業務場景。比如,有這樣一個需求:“如果連續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小時後生效;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章