总结定时器设计方法_Swoole 定时器实现毫秒级任务调度

Swoole 定时器简介

Swoole 提供了异步高精度定时器功能,该功能类似 JavaScript 的 setInterval/setTimeout,粒度为毫秒级,底层基于 epoll_wait(异步)和 setitimer(同步)实现,数据结构使用最小堆。定时器的添加和删除,全部为内存操作,无 IO 消耗,因此性能是非常高的。

需要注意的是,Swoole 实现的定时器与 PHP 自带的 pcntl_alarm 不同,pcntl_alarm 是基于时钟信号和 tick 函数实现的,最大仅支持到秒,此外不支持同时设定多个定时器程序,性能也很差。

Swoole 提供了两种类型的定时器,一种是每隔一定时间执行的定时器,一种是指定时间后一次性执行的定时器,下面我们分别看看这两种定时器的实现和使用。

Swoole 定时器的使用

间隔时钟定时器

在 Swoole 中,我们可以通过 Timer::tick 实现间隔时钟定时器,该定时器会每隔指定时间触发回调函数的执行:

SwooleTimer::tick(1000, function () {

echo "Swoole 很棒n";

});

Swoole 定时器需要在 PHP CLI 模式下才能运行,上述代码的意思是每隔 1000ms 执行一次回调函数打印字符串:

02179533eb5b2881ba794db4335d9277.png

指定时钟定时器

此外,还可以通过 Timer::after 定义一个指定时间后执行的定时器,与间隔时钟定时器不同,这种定时器是一次性的,执行完成后就会销毁:

SwooleTimer::after(3000, function () {

echo "Laravel 也很棒n";

});

上述定义器的含义是 3000ms 后执行指定的回调函数,并且执行之后就退出程序:

f81e4effb500c59af25c0915e22ed254.png

清除定时器

对于一次性执行的指定时钟定时器,不用关心清除问题,而对于间隔时钟定时器,如果不定义清楚逻辑的话,会永远执行下去,直到程序退出,我们可以通过 Timer::clear 删除定时器来达到清除的目的,在具体实现的时候,将定时器 ID 传入该方法即可,上述两种定时器定义方法都会返回对应的定时器 ID, 因此,清除定义器可以作用于上述两种定时器:

/*$timerId = SwooleTimer::tick(1000, function () {

echo "Swoole 很棒n";

});*/

$timerId = SwooleTimer::after(1000, function () {

echo "Laravel 也很棒n";

});

SwooleTimer::clear($timerId);

这种情况下,两个定时器都不会调用,对于间隔时钟定时器,还可以这么清除:

$count = 0;

SwooleTimer::tick(1000, function ($timerId, $count) {

global $count;

echo "Swoole 很棒n";

$count++;

if ($count == 3) {

SwooleTimer::clear($timerId);

}

}, $count);

这种情况下,定时器会在执行三次后退出:

721c0dab5ea225d02e07c2ebdb6614b7.png

基于 Swoole 定时器实现毫秒级任务调度

我们可以基于 Swoole 定时器实现毫秒级调度任务来替代 Linux 自带的 Cron Job(最小粒度是分钟),以 Laravel 框架为例,我们可以基于 LaravelS 扩展包来定义一个继承自 CronJob 基类的调度任务类:

<?php

namespace AppJobsTimer;

use Hhxsv5LaravelSSwooleTimerCronJob;

use IlluminateSupportFacadesLog;

class TestCronJob extends CronJob

{

protected $i = 0;

// 该方法可类比为 Swoole 定时器中的回调方法

public function run()

{

Log::info(__METHOD__, ['start', $this->i, microtime(true)]);

$this->i++;

Log::info(__METHOD__, ['end', $this->i, microtime(true)]);

if ($this->i == 3) { // 总共运行3次

Log::info(__METHOD__, ['stop', $this->i, microtime(true)]);

$this->stop(); // 清除定时器

}

}

// 每隔 1000ms 执行一次任务

public function interval()

{

return 1000; // 定时器间隔,单位为 ms

}

// 是否在设置之后立即触发 run 方法执行

public function isImmediate()

{

return false;

}

}

然后到 config/laravels.php 配置文件中修改 timer 配置项如下:

'timer' => [

'enable' => true,

'jobs' => [

// Enable LaravelScheduleJob to run `php artisan schedule:run` every 1 minute, replace Linux Crontab

// Hhxsv5LaravelSIlluminateLaravelScheduleJob::class,

// Two ways to configure parameters:

// [AppJobsXxxCronJob::class, [1000, true]], // Pass in parameters when registering

AppJobsTimerTestCronJob::class, // Override the corresponding method to return the configuration

],

'max_wait_time' => 5, // Max waiting time of reloading

],

接下来,我们启动或重启 Swoole 服务器:

php bin/laravels start

在 storage/logs 下的最新日志里就可以看到上述定时任务的输出了:

[2020-11-10 22:37:20] local.INFO: AppJobsTimerTestCronJob::run ["start",0,1559140640.694025]

[2020-11-10 22:37:20] local.INFO: AppJobsTimerTestCronJob::run ["end",1,1559140640.752043]

[2020-11-10 22:37:21] local.INFO: AppJobsTimerTestCronJob::run ["start",1,1559140641.752905]

[2020-11-10 22:37:21] local.INFO: AppJobsTimerTestCronJob::run ["end",2,1559140641.754724]

[2020-11-10 22:37:22] local.INFO: AppJobsTimerTestCronJob::run ["start",2,1559140642.694884]

[2020-11-10 22:37:22] local.INFO: AppJobsTimerTestCronJob::run ["end",3,1559140642.696726]

[2020-11-10 22:37:22] local.INFO: AppJobsTimerTestCronJob::run ["stop",3,1559140642.698137]

我们重点关注三个「start」的日志记录,对应的时间戳正好相差 1 秒,符合我们的预期。注意定时器在执行的过程中可能存在一定误差,所以这个毫秒数是数量级级别的,并不是完全相等,而且时间单位越小,误差可能越大,不过这已经比 Linux 自带的任务调度只能精确到分钟要好很多了。

如果你喜欢我写的技术文章以及面试总结,欢迎关注收看我的视频,并且点赞、收藏、关注我哦。

我是luke,感谢你的关注!

据说点赞,喜欢,收藏了的小伙伴进阶涨薪,面试必过,拿到心仪offer!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值