延時任務有別於定時任務,定時任務往往是固定週期的,有明確的觸發時間。而延時任務一般沒有固定的開始時間,它常常是由一個事件觸發的,而在這個事件觸發之後的一段時間內觸發另一個事件。
我們不妨來設定一個實際的場景,電商系統下單成功之後如果15分鐘未支付成功,就係統自動取消訂單。
我們先來實現程式碼,然後再來詳細說明:
// 我們定義一個abstract,定義兩個方法,startAfter和startAt
<?php
abstract class DelayTask
{
const DELAY_TASK = 'delayTask';
/**
* 延時時間,在觸發時間後多久執行
* @param $pushDelayTime
*/
public function startAfter(int $pushDelayTime)
{
RedisManager::getRedis()->zAdd(self::DELAY_TASK, time() + $pushDelayTime, serialize($this));
}
/**
* 定時時間,在未來某時刻執行
* @param $pushDelayAt
*/
public function startAt(int $pushDelayAt)
{
RedisManager::getRedis()->zAdd(self::DELAY_TASK, $pushDelayAt, serialize($this));
}
abstract function run();
}
class TestDelayTask extends DelayTask
{
public function __construct($id)
{
$this->id = $id;
}
public function run()
{
$file = 'text.txt';//要寫入檔案的檔名(可以是任意檔名),如果檔案不存在,將會建立一個
$content = "寫入的內容".time()."\n";
if($f = file_put_contents($file, $content,FILE_APPEND)){// 這個函式支援版本(PHP 5)
echo "寫入成功。<br />";
}
}
}
RedisManager是封裝的一個單例模式實現的redis類,我們也貼出程式碼,然後再對上面的程式碼做一些說明。
<?php
class RedisManager
{
private static $instance = null;
private function __construct()
{
self::$instance = new \Redis();
$config = require 'redis.config.php';
self::$instance->connect($config['host'], $config['port'], $config['timeout']);
if (isset($config['password'])) {
self::$instance->auth($config['password']);
}
}
/**
* 獲取靜態例項
*/
public static function getRedis()
{
if (!self::$instance) {
new self;
}
return self::$instance;
}
/**
* 禁止clone
*/
private function __clone()
{
}
}
有序集合
redis 127.0.0.1:6379> ZADD saif 1 redis
(integer) 1
redis 127.0.0.1:6379> ZADD saif 2 mongodb
(integer) 1
redis 127.0.0.1:6379> ZADD saif 4 mysql
(integer) 0
redis 127.0.0.1:6379> ZRANGE saif 0 10 WITHSCORES
1) "redis"
2) "1"
3) "mongodb"
4) "2"
5) "mysql"
6) "4"
如果你有redis視覺化工具,你會發現有序集合儲存的結構是這樣:
row | value | score |
---|---|---|
1 | redis | 1 |
2 | mongodb | 2 |
3 | mysql | 4 |
我們再來看一下程式碼,我們使用時間戳作為分值,使用物件作為值,儲存到Redis有序集合。
file_put_contents ( string $filename , mixed $data [, int $flags = 0 [, resource $context ]] ) : int — 將一個字串寫入檔案
執行
我們先嚐試寫入任務:
(new TestDelayTask(4))->startAfter(900);
(new TestDelayTask(4))->startAfter(120);
(new TestDelayTask(4))->startAfter(600);
(new TestDelayTask(3))->startAt(158120384);
(new TestDelayTask(3))->startAt(158420380);
(new TestDelayTask(3))->startAt(158120900);
執行程式碼:
class DelayTaskTask
{
const QueneName = 'delayTask';
private $currentTime;
private $once = 5;
public function run()
{
$this->currentTime = time();
error_reporting(error_reporting() & ~E_WARNING);
while (true) {
// 每次取出5條
$list = RedisManager::getRedis()->zRange(self::QueneName, 0, $this->once, true);
if (!empty($list)) {
foreach ($list as $val=>$score) {
if ($score < $this->currentTime) {
unserialize($val)->run();
RedisManager::getRedis()->zDelete(self::QueneName, $val);
} else {
break 2;
}
}
} else {
break;
}
}
}
}
(new DelayTaskTask())->run();
我們可以設定一個定時任務,每分鐘執行一次上述程式碼。
我們執行之後,會發現一旦過了我們設定的時間,text.txt就不斷有文字寫入了。
程式碼比較雜,我有一個demo程式碼,大家可以檢視。
這樣,我們就利用Redis有序集合,完成了一個很基礎的延時任務。
問題
- 加入run方法程式碼執行時間過長,一分鐘執行一次有什麼問題嗎?
- 每分鐘執行一次,間隔有點長,能不能優化呢?
本作品採用《CC 協議》,轉載必須註明作者和本文連結