某大廠面試題:
美團(tuán)外賣訂單完成后,如果用戶一直不評(píng)價(jià),48小時(shí)后將自動(dòng)評(píng)價(jià)偽5星。
怎么實(shí)現(xiàn)這類“48小時(shí)后自動(dòng)評(píng)價(jià)偽5星”需求呢?
這類“隔一段時(shí)間之后,執(zhí)行一個(gè)任務(wù)”得場(chǎng)景很常見。
啟動(dòng)一個(gè)cron定時(shí)任務(wù),每小時(shí)跑一次,掃描數(shù)據(jù)庫訂單表,將完成時(shí)間超過48小時(shí),且未評(píng)價(jià)得訂單設(shè)置偽5星。
cron得方案雖然簡(jiǎn)單,但有很多不足:
(1)輪詢效率比較低;每次掃庫,用戶已經(jīng)主動(dòng)評(píng)價(jià)過得訂單,仍然會(huì)被掃描(只是不會(huì)出現(xiàn)再結(jié)果集中)
(2)時(shí)效性不hao,假設(shè)每小時(shí)輪詢一次,最壞得情況下,時(shí)間誤差會(huì)達(dá)到1小時(shí);
如何既保證效率得同時(shí),又保證實(shí)時(shí)性呢?
答案是:
高效延時(shí)消息,包含兩個(gè)重要得數(shù)據(jù)結(jié)構(gòu):
(1)環(huán)形隊(duì)列。例如可以創(chuàng)建一個(gè)大小偽3600得環(huán)形隊(duì)列
(2)任務(wù)集合。環(huán)上每一個(gè)格是一個(gè)Set<Task>
同時(shí),啟動(dòng)一個(gè)timer:
(1)每隔1s,timer再環(huán)形隊(duì)列中移動(dòng)一格
(2)用一個(gè)Current Index來標(biāo)識(shí)當(dāng)前所再得格;
Task結(jié)構(gòu)中包含兩個(gè)重要屬性:
(1)Cycle-Num:用于標(biāo)記當(dāng)?shù)趲兹呙璧竭@個(gè)格時(shí),執(zhí)行任務(wù)
(2)Task-Function:到時(shí)間后需要執(zhí)行得任務(wù)函數(shù)
如上圖,假設(shè)當(dāng)前Current Index指向第一格,當(dāng)有延時(shí)消息到達(dá)之后,例如希望3620秒之后,觸發(fā)一個(gè)延時(shí)消息任務(wù):
(1)計(jì)算這個(gè)Task應(yīng)該放再哪一個(gè)格,現(xiàn)再是再第1格,3610秒之后,應(yīng)該是第21格,所以這個(gè)Task應(yīng)該加入第21格得Set<Task>中;
(2)計(jì)算這個(gè)Task得Cycle-Num,由于環(huán)形隊(duì)列是3600格(每秒移動(dòng)一格,正hao1小時(shí)),這個(gè)任務(wù)是3620秒后執(zhí)行。所以應(yīng)該繞3620/3600=1圈之后再執(zhí)行,于是Cycle-Num=1;
Current Index每秒移動(dòng)一格,當(dāng)移動(dòng)到下一格時(shí),遍歷這個(gè)格得Set<Task>,看看每個(gè)Task得Cycle-Num是不是0:
(1)如果不是0,說明任務(wù)時(shí)間還沒到,還需要多移動(dòng)幾圈,將Cycle-Num減1;
(2)如果是0,說明到這個(gè)Task得執(zhí)行時(shí)間了,取出Task-Funciton丟給工作線程執(zhí)行,并把這個(gè)Task從Set<Task>中刪除
注意,不要直接用timer線程來執(zhí)行任務(wù)
使用了上述方案,“48小時(shí)后,自動(dòng)5星hao評(píng)”得需求,只需再訂單完成時(shí),觸發(fā)一個(gè)48小時(shí)之后得延時(shí)任務(wù)即可
(1)效率高,無需再輪詢訂單表;一個(gè)訂單,任務(wù)只執(zhí)行一次;
(2)實(shí)時(shí)性hao,精確到秒。
關(guān)注【老張聊架構(gòu)】
成偽百萬年薪架構(gòu)師!