应用篇:2.延时任务
PHP DIY系列–一起手写一个api框架
延时任务有别于定时任务,定时任务往往是固定周期的,有明确的触发时间。而延时任务一般没有固定的开始时间,它常常是由一个事件触发的,而在这个事件触发之后的一段时间内触发另一个事件。
我们不妨来设定一个实际的场景,电商系统下单成功之后如果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方法代码执行时间过长,一分钟执行一次有什么问题吗?
- 每分钟执行一次,间隔有点长,能不能优化呢?
应用篇:2.延时任务
https://blog.puresai.com/2020/02/22/phpframe14/