http://blog.csdn.net/orangleliu/article/details/52038092
用Redis完成類似
at
命令的功能,例如訂單24小時后沒有支付自動關閉,定時發郵件,主要說下任務生成之后怎么觸發消費。
使用 有序集合
思路: 使用sorted Sets的自動排序, key 為任務id,score 為任務計划執行的時間戳,這樣任務在加入sets的時候已經按時間排序,這樣每隔1s(或者其他間隔)去取出sets頂部的數據,小於當前時間的可以通過pop取出來然后去執行。
redis模擬
127.0.0.1:6379> zadd cron 10001 task1 (integer) 1 127.0.0.1:6379> zadd cron 9001 task2 (integer) 1 127.0.0.1:6379> zadd cron 29001 task3 (integer) 1 127.0.0.1:6379> ZRANGE cron 0 -1 withscores 1) "task2" 2) "9001" 3) "task1" 4) "10001" 5) "task3" 6) "29001" 假設當前的時間戳是 15000 127.0.0.1:6379> ZRANGEBYSCORE cron -inf 15000 1) "task2" 2) "task1" 127.0.0.1:6379> ZREM cron task2 (integer) 1 127.0.0.1:6379> ZREM cron task1 (integer) 1 127.0.0.1:6379> ZRANGE cron 0 -1 withscores 1) "task3" 2) "29001"
- 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
- 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
上面的測試直接把小於當前時間戳的所有任務都做了一遍,會有些bug,例如找個定時監測程序掛了2天, 對於某些任務可能有效期只有那么10分鍾,重新啟動定時監測程序,就會把過期任務也做了一遍, 那么我們選取任務的時候范圍要更精確一些。
如果當前時間戳是 29100 可以取到 task3 127.0.0.1:6379> ZRANGEBYSCORE cron 28500 29100 1) "task3" 如果當前時間戳是 30600 就無法取到 task3, 注意對過期任務的清理 127.0.0.1:6379> ZRANGEBYSCORE cron 30000 30600 (empty list or set)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
利用鍵過期通知
思路: reids 2.8 有一種 鍵空間通知的機制 Keyspace Notifications (強烈推薦看一遍), 允許客戶端去訂閱一些key的事件,其中就有 key過期的事件,我們可以把 key名稱設置為 task的id等標識(這種方式value的值無法取到,所以只用key來識別任務),expire設置為計划要執行的時間,然后開啟一個客戶端來訂閱消息過期事件,然后處理task。
需要更改redis配置,注意版本要在2.8.0以上, 如果沒有這個key 請添加上,如果有請更改為下面這樣
notify-keyspace-events Ex
- 1
- 1
重啟redis,第一個窗口, 開啟訂閱
liuzhizhi@lzz-rmbp|redis_test # redis-cli --csv psubscribe '__keyevent@0__:expired' Reading messages... (press Ctrl-C to quit) "psubscribe","__keyevent@0__:expired",1 "pmessage","__keyevent@0__:expired","__keyevent@0__:expired","task1" "pmessage","__keyevent@0__:expired","__keyevent@0__:expired","task2"
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
第二個窗口 設置key
127.0.0.1:6379> set task1 xx OK 127.0.0.1:6379> EXPIRE task1 5 (integer) 1 127.0.0.1:6379> set task2 xx OK 127.0.0.1:6379> EXPIREAT task2 1469525560 (integer) 1
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
當key過期的時候就看到第一個窗口的通知了,訂閱的key __keyevent@<db>__:expired
這個格式是固定的,db代表的是數據庫的編號,由於訂閱開啟之后這個庫的所有key過期時間都會被推送過來,所以最好單獨使用一個數據庫來進行隔離。
小結
以上就是使用redis來處理定時任務的兩種思路,常用的編程語言應該都比較容易實現。