swoole定时执行php代码,Swoole定时器实现Linux的Crontab

Crontab

crontab是要定期运行的命令的列表,以及用于管理该列表的命令的名称。 crontab代表“cron表”,因为它使用作业调度程序cron来执行任务; cron本身以“chronos”命名,希腊语为时间。cron是系统进程,它会根据一个安排的时间表为你自动执行任务。该计划称为crontab,它也是用于编辑该计划的程序的名称

Crontab配置语法

* * * * * 要执行的命令

- - - - -

| | | | |

| | | | ----- 星期几 (0 - 7) (星期天=0 或者 7)

| | | ------- 月份 (1 - 12)

| | --------- 天数 (1 - 31)

| ----------- 小时 (0 - 23)

------------- 分钟 (0 - 59)

Note:

每天八点执行: 0 8 * * * php Eight.php

每月10号8点执行: 0 8 10 * * php Eight.php

星期天8点执行:0 8 * * 7 php Eight.php

Swoole定时器

在swoole扩展中提供一个叫[swoole_timer_tick][3]函数,它的作用是在设定的毫秒内重复执行回调函数(当然你也可以清除掉这个定时器)。这个和JavaScript的setTimeout和setInterval类似。swoole_timer_tick底层是[epoll]模型和堆结构实现。

//file:swoole.c line:857 这个函数在php启动时候执行一次,

swoole_init();

//file:swoole_timer.c line: 249

swTimer_node *tnode = SwooleG.timer.add(&SwooleG.timer, ms, persistent, cb, timer_func);

//file:Timer.c line:161

tnode->heap_node = swHeap_push(timer->heap, tnode->exec_msec, tnode);

//这段代码是整个swoole的核心,file:ReactorEpoll.c line:215

while (reactor->running > 0)

{

...

if (reactor->onFinish != NULL)

{

reactor->onFinish(reactor);

}

}

这个是使用gdb调试swoole_timer_tick的调用堆栈。

Breakpoint 5, swReactor_onTimeout (reactor=0x15c1880) at /home/soft/swoole/src/reactor/ReactorBase.c:194

194 {

(gdb) bt

#0 swReactor_onTimeout (reactor=0x15c1880) at /home/soft/swoole/src/reactor/ReactorBase.c:194

#1 0x00007fffe82a2ff1 in swReactorEpoll_wait (reactor=0x15c1880, timeo=) at /home/soft/swoole/src/reactor/ReactorEpoll.c:289

#2 0x00007fffe826d874 in php_swoole_event_wait () at /home/soft/swoole/swoole_event.c:212

#3 0x0000000000843779 in zend_call_function (fci=fci@entry=0x7fffffffcd90, fci_cache=0x7fffffffccd0, fci_cache@entry=0x0)

at /home/soft/php-7.1.5/Zend/zend_execute_API.c:869

#4 0x0000000000843cd5 in _call_user_function_ex (object=object@entry=0x0, function_name=,

retval_ptr=retval_ptr@entry=0x7fffffffcdf0, param_count=, params=, no_separation=no_separation@entry=1)

at /home/soft/php-7.1.5/Zend/zend_execute_API.c:672

#5 0x000000000075c99f in user_shutdown_function_call (zv=) at /home/soft/php-7.1.5/ext/standard/basic_functions.c:4982

#6 0x0000000000863aab in zend_hash_apply (ht=0x7fffefa561f8, apply_func=apply_func@entry=0x75c8e0 )

at /home/soft/php-7.1.5/Zend/zend_hash.c:1508

#7 0x000000000075f726 in php_call_shutdown_functions () at /home/soft/php-7.1.5/ext/standard/basic_functions.c:5066

#8 0x00000000007f2c65 in php_request_shutdown (dummy=dummy@entry=0x0) at /home/soft/php-7.1.5/main/main.c:1819

#9 0x00000000008ebe1b in do_cli (argc=2, argv=0x11f4c20) at /home/soft/php-7.1.5/sapi/cli/php_cli.c:1160

#10 0x0000000000448820 in main (argc=2, argv=0x11f4c20) at /home/soft/php-7.1.5/sapi/cli/php_cli.c:1381

onTimeout的调用是通过epoll_wait来的,之后调用swReactor_onTimeout_and_Finish函数, 然后调用php_swoole_onInterval函数这个会调用我们定义的回调函数。

/**

* execute when reactor timeout and reactor finish

*/

static void swReactor_onTimeout_and_Finish(swReactor *reactor)

{

//check timer

if (reactor->check_timer)

{

swTimer_select(&SwooleG.timer); //这个函数进去之后就是回调我们定义的函数

}

...

}

各位可以用gdb跟踪下swoole_timer_tick整个运行流程。

Crond实现

我们这里的实现主要是任务控制部分,任务的执行部分交给Symfony的Process`类库去处理。

Task类

既然用得crontab配置, 那肯定是要解析配置。我们在类的构造函数去解析当前任务的配置主要包括:

星期几

某月

某日

某小时

某分钟

执行命令

当内容为 * 号时,说明当前配置列所有段都执行。

class Task

{

private $taskString;

private $min;

private $hour;

private $day;

private $month;

private $week;

private $command;

private $process;

private $runTime;

/**

*@var string $taskString example: 10 * * * * php example.php

*/

public function __construct(string $taskString)

{

$this->taskString = $taskString;

$this->runTime = time();

$this->initialize();

}

/**

* 初始化任务配置

*/

private function initialize()

{

//过滤多余的空格

$rule = array_filter(explode(" ", $this->taskString), function($value) {

return $value != "";

});

if (count($rule) < 7) {

throw new ErrorException("'taskString' parse failed");

}

$this->min = $this->format($rule[0], 'min');

$this->hour= $this->format($rule[1], 'hour');

$this->day = $this->format($rule[2], 'day');

$this->month = $this->format($rule[3], 'month');

$this->week= $this->format($rule[4], 'week');

$this->command = array_slice($rule, 5);

}

}

在初始化配置时候使用了format函数格式化列的内容,因为我知道列的内容是可以配置成1, 2, 3、*/10, 1-10这种形式,所以我们能过这个函数统一处理。函数接收到两个参数一个为列值,一个为列名。

private function format($value, $field)

{

if ($value === '*') {

return $value;

}

if (is_numeric($value)) {

return [$this->checkFieldRule($value, $field)];

}

$steps = explode(',', $value);

$scope = [];

foreach ($steps as $step) {

if (strpos($step, '-') !== false) {

$range = explode('-', $step);

$scope = array_merge($scope, range(

$this->checkFieldRule($range[0], $field),

$this->checkFieldRule($range[1], $field)

));

continue;

}

if (strpos($step, '/') !== false) {

$inter = explode('/', $step);

$confirmInter = isset($inter[1]) ? $inter[1] : $inter[0];

if ($confirmInter === '/') {

$confirmInter = 1;

}

$scope = array_merge($scope, range(

constant('FIRST_' . strtoupper($field)),

constant('LAST_' . strtoupper($field)),

$confirmInter

));

continue;

}

$scope[] = $step;

}

return $scope;

}

format返回*或者一个数组, 因为1-10这种实际就是1到10的一个数组,*/2 这种实际也是一个有步长的数组,使用range就实现了这个数组,1,2,3,30,31,32 这种就是直接分割成数组。使用了checkFieldRule来验证值的合法性.

private function checkFieldRule($value, $field)

{

$first = constant('FIRST_' . strtoupper($field));

$last = constant('LAST_' . strtoupper($field));

if ($value < $first) {

return $first;

}

if ($value > $last) {

return $last;

}

return (int) $value;

}

函数使用了几个常量来验证。

define('FIRST_MIN', 0);

define('LAST_MIN', 59);

define('FIRST_HOUR', 0);

define('LAST_HOUR', 23);

define('FIRST_DAY', 1);

define('LAST_DAY', 31);

define('FIRST_MONTH', 1);

define('LAST_MONTH', 12);

define('FIRST_WEEK', 0);

define('LAST_WEEK', 6);

为任务类添加一个执行命令的方法, 在任务达到条件时执行此方法来执行命令,$this->process->start()以异步方式运行任务

public function run()

{

if (null === $this->process) {

$this->process = new Process(implode(" ", $this->command));

}

$this->process->start();

}

因为类中的属性都是私有的需要创建几个辅助函数来处理set和get问题不使用__get和__set。

public function getTimeAttribute($attribute)

{

if (!in_array($attribute, ['min', 'hour', 'day', 'month', 'week', 'runTime'])) return null;

return $this->{$attribute} ?? null;

}

public function setRunTime($time)

{

$this->runTime = $time;

}

Job类

Job类是一个实现迭代器接口的类,方遍我们管理Task。

class Job implements Iterator

{

private $position = 0;

private $jobs = [];

public function addJob(Task $task)

{

$this->jobs[] = $task;

}

public function current()

{

return $this->jobs[$this->position];

}

public function key()

{

return $this->position;

}

public function next()

{

++$this->position;

}

public function rewind()

{

$this->position = 0;

}

public function valid()

{

return isset($this->jobs[$this->position]);

}

}

swoole_timer_tick回调

在回调我们需要从Job类中把所有Task拿出来,检测Task是否可以执行。

swoole_timer_tick(1000, function($timeId, $params = null) use ($jobs, &$prevTime) {

$current = time();

//week, month, day, hour, min

$ref = explode('|', date('w|n|j|G|i', $current));

if ($prevTime) {

$prevMin = date('i', $prevTime);

if ($prevMin == $ref[4]) {

return true;

}

}

foreach ($jobs as $task) {

$ready = 0;

//$diff = $task->getTimeAttribute('runTime') - $current;

//对应上面的$ref数组

foreach (['week', 'month', 'day', 'hour', 'min'] as $key => $field) {

$value = $task->getTimeAttribute($field);

if ($value === '*') {

$ready += 1;

continue;

}

$ready += in_array($ref[$key], $value) ? 1: 0;

}

if (5 === $ready) {

//执行任务

$task->run();

//更新运行时间

$task->setRunTime($current);

}

}

$prevTime = $current;

return true;

//swoole_timer_clear($timeId);

});

Note:

我们这里设置的是秒级别,而我们的任务是分级别。而且这样的话我们的任务会在当前分钟重复执行60次,这是我们不愿意看到的。所以程序里面定义了一个$prevTime的变量,来控制这次执行是不是和上次同一份钟,用秒来定义是精确任务执行时间。当任务达到了当前周、月、日、时、分条件时才执行任务。

完成

根据上面的实现,重新规范下代码。

dae9639ee28e208944d0a5934016753c.png

测试

新建一个crontab和example.php文件 内容为下面代码。

#!/usr/bin/php

include __DIR__ . '/vendor/autoload.php';

use Cron\Cron;

$command = "php " . __DIR__ . '/example.php';

$crontab = <<

*/1 * * * * $command

EOF;

$cron = new Cron( ($tasks = explode("\n", $crontab)));

$cron->start();

//example.php文件内容

$time = date('Y-m-d H:i:s');

file_put_contents("crontab.txt", $time . "\r\n", FILE_APPEND);

执行命令

[root@meshell swoole-crontab]# php crontab

效果

7184e501d9fdc0ab65e378d18886e59c.png

项目源码

推荐阅读

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Swoole-Crontab(基于Swoole扩展) 1.概述 基于swoole定时器程序,支持秒级处理. 异步多进程处理。 完全兼容crontab语法,且支持秒的配置,可使用数组规定好精确操作时间 请使用swoole扩展1.7.9-stable及以上版本.Swoole 支持worker处理redis队列任务 2.配置的支持 具体配置文件请看 src/config/crontab.php 介绍一下时间配置 0 1 2 3 4 5 | | | | | | | | | | | ------ day of week (0 - 6) (Sunday=0) | | | | ------ month (1 - 12) | | | -------- day of month (1 - 31) | | ---------- hour (0 - 23) | ------------ min (0 - 59) -------------- sec (0-59)[可省略,如果没有0位,则最小时间粒度是分钟] 3.帮助信息 * Usage: /path/to/php main.php [options] -- [args...] * -h [--help] 显示帮助信息 * -p [--pid] 指定pid文件位置(默认pid文件保存在当前目录) * -s start 启动进程 * -s stop 停止进程 * -s restart 重启进程 * -l [--log] log文件夹的位置 * -c [--config] config文件的位置 * -d [--daemon] 是否后台运行 * -r [--reload] 重新载入配置文件 * -m [--monitor] 监控进程是否在运行,如果在运行则不管,未运行则启动进程 * --worker 开启worker 可以针对redis队列读取并编写处理逻辑 * --tasktype task任务获取类型,[file|mysql] 默认是file * --checktime 默认精确对时(如果精确对时,程序则会延时到分钟开始0秒启动) 值为false则不精确对时 4.worker进程配置 在src/config/worker.php 中写入配置,并且启动的时候加上 --worker选项就能启动worker工作进程 配置如下: return array( //key是要加载的worker类名 "ReadBook"=>array( "name"=>"队列1", //备注名 "processNum"=>1, //启动的进程数量 "redis"=>array( "host"=>"127.0.0.1", // redis ip "port"=>6379, // redis端口 "timeout"=>30, // 链接超时时间 "db"=>0, // redis的db号 "queue"=>"abc" // redis队列名 ) ) ); 具体的业务逻辑在src/worker/ 文件夹下。可以自己定义业务逻辑类,只需要继承WorkerBase.class.php中的WorkerBase基类就可以 5.例子 你可以在配置文件中加上以下配置: return array( 'taskid1' => array( 'taskname' => 'php -i', //任务名称 'rule' => '* * * * * *',//定时规则,可以使用数组精确设置时间 如:array("22:18","2015-11-11 00:00:00 ","10:20:39") "unique" => 2, //排他数量,如果已经有这么多任务在执行,即使到了下一次执行时间,也不执行 'execute' => 'Cmd',//命令处理类 'args' => array( 'cmd' => 'php -i',//命令 "ext": "" ), ), ); 然后去到src目录下,执行 /path/to/php main.php -s start 执行完成以后你就可以在/tmp/test.log看到输出了,每秒输出一次 如果你需要写自己的代码逻辑,你也可以到plugin目录下,实现一个PluginBase.class.php接口的类. 在其中写自己的逻辑代码。 6.TODO 分布式运行 restful增删改任务 标签:swoole
Swoole直播的实现代码比较复杂,这里提供一个简单的示例代码,仅供参考: ```php // 创建swoole服务器 $server = new Swoole\WebSocket\Server("0.0.0.0", 9501); // 监听WebSocket连接事件 $server->on('open', function (Swoole\WebSocket\Server $server, $request) { echo "WebSocket连接成功\n"; }); // 监听WebSocket消息事件 $server->on('message', function (Swoole\WebSocket\Server $server, $frame) { // 接收客户端发送的消息 $data = json_decode($frame->data, true); $type = isset($data['type']) ? $data['type'] : ''; $content = isset($data['content']) ? $data['content'] : ''; switch ($type) { case 'login': // 用户登录信息处理 break; case 'message': // 消息处理 break; case 'heartbeat': // 心跳处理 break; default: // 其他处理 break; } }); // 监听WebSocket关闭事件 $server->on('close', function (Swoole\WebSocket\Server $server, $fd) { echo "WebSocket关闭连接\n"; }); // 启动swoole服务器 $server->start(); ``` 上述代码中,我们创建了一个WebSocket服务器,并监听了三个事件:连接事件、消息事件和关闭事件。当有客户端连接到服务器时,会触发`open`事件;当有客户端发送消息时,会触发`message`事件;当有客户端关闭连接时,会触发`close`事件。 在消息事件中,我们可以根据客户端发送的消息类型进行不同的处理。例如,当客户端发送登录消息时,我们可以将用户信息保存到服务器中,以便后续使用;当客户端发送聊天消息时,我们可以将消息广播给所有在线用户。 需要注意的是,在实际的直播应用中,还需要考虑到视频流的推拉流、音视频混流等问题,这些问题需要使用专门的直播技术来解决。本示例代码仅提供了WebSocket服务器的基本实现,实际应用中还需要根据具体需求进行更加详细的开发。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值