背景
PHP沒有定時器,依托的都是crontab這樣的系統工具,也沒有go中defer這樣的延時方法,本文介紹幾種PHP寫延時隊列的幾種姿勢。
延時隊列的定義
普通的隊列是先進先出,但是延時隊列并不是,而是加上了時間這一權重。希望到達時間點的先執行。
從某種意義上來講,延遲隊列的結構并不像一個隊列,而更像是一種以時間為權重的有序堆結構。
Hash
将key使用一個唯一辨別,保證每個任務都不重複,也友善删除,然後value中添加需要調用的函數名和時間戳,以及參數。
沒秒進行周遊,然後将時間到的取出來執行,再删除。
入隊:hSet key:uuid value:{timestamp,function,param}
出隊:timestamp > now do function(param)
問題很明顯,需要周遊,hGetAll是禁忌法術。
ZSet
前面談到了,其實延時隊列就是一種有序堆結構,就是需要加上一個時間權重,那麼,有序集合不就是這樣的麼?
入隊:ZADD KEY timestamp task ,我們将需要處理的任務,按其需要延遲處理時間作為 Score 加入到 ZSet 中。
出隊:ZRANGEBYSCORE KEY -inf +inf limit 0 1 WITHSCORES。這樣就能取出需要執行的任務了。執行完後删除:Zremrangebyscore KEY -inf +inf limit
時間輪
其實以上的算法,都有個小問題,同一時間線的任務先後問題,比如都是淩晨00執行的,怎麼誰先誰後?因為任務是有優先級或者順序的,當然也可以按優先級設定多個key,思路有很多。
這裡在介紹一種算法,也是很多消息隊列軟體使用的,時間輪算法。
其實也很簡單,就是每個時間點放一個隊列,然後用一個任務去掃描時間輪,就像時鐘一樣,這樣就能到點執行對應的任務了。

比如任務掃描到了2,需要添加一個延時3秒的任務,就直接添加到5上面。
當然對于時間粒度不同,我們肯定要設定多個時間輪,就像時針分針秒針。
這樣的好處是什麼?
- 前面兩種方式,說到底,需要周遊+排序,而時間輪,隻需要逐漸掃描逐漸取出任務就好了。效率上高了很多,任務越多越明顯。