thinkphp6+swoole实现crontab项目定时任务

背景

之前做定时任务,都是通过linux的crontab来实现,但是定时任务多了,就不好管理。一个是是定时的备注不好写,另一个是定时日志的结果没法存入数据库。

需求

1、按crontab的格式设置定时任务

2、执行结果存入数据库,包括执行结果,执行时间

3、秒级的定时

安装依赖

composer require dragonmantank/cron-expression

代码实现

1、添加一个自定义指令 Task

php think make:command Task task

2、在config/console.php中注册该指令

// 指令定义

'commands' => [

        'task' => \app\command\Task::class,

],

3、完善指令代码

<?php
declare (strict_types = 1);

namespace app\command;

use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\facade\Db;
use Cron\CronExpression;
use Curl\Curl;
use think\facade\Config;
use Swoole\Coroutine;
use function Swoole\Coroutine\run;
use app\model\system\SystemTaskLog;

class Task extends Command
{
    protected function configure()
    {
        // 指令配置
        $this->setName('task')
            ->setDescription('the task command');
    }

    protected function execute(Input $input, Output $output)
    {
        $output->writeln('Execute start!'.Date('Y-m-d H:i:s'));
        run(function () use($output) {
            $time = time();

            //筛选未过期且未完成的任务
            $crontabList = Db::name('system_task')->where('status', '=', '1')->order('weigh DESC,id DESC')->select();

            // 准备需要执行的crontab
            $out_crontab_arr = [];
            $max_crontab_arr = [];
            $exe_crontab_arr = [];

            foreach ($crontabList as $key => $crontab) {
                if ($time < $crontab['begintime']) {
                    $output->writeln('任务未开始');
                    continue;
                }
                if ($crontab['maximums'] && $crontab['executes'] > $crontab['maximums']) {
                    //任务已超过最大执行次数
                    $output->writeln('任务已超过最大执行次数');
                    array_push($max_crontab_arr,$crontab);
                } else {
                    if ($crontab['endtime'] > 0 && $time > $crontab['endtime']) {
                        //任务已过期
                        $output->writeln('任务已过期');
                        array_push($out_crontab_arr,$crontab);
                    } else {
                        //重复执行
                        //如果未到执行时间则继续循环
                        $cron = CronExpression::factory($crontab['schedule']);
                        if (!$cron->isDue(date("YmdHi", $time)) || date("YmdHi", $time) === date("YmdHi", $crontab['executetime'])) {
                            $output->writeln('未到执行时间则继续循环');
                            continue;
                        }else{
                            array_push($exe_crontab_arr,$crontab);
                        }
                    }
                }
            }
            if($out_crontab_arr){
                $out_id_arr = array_column($out_crontab_arr,'id');
                Db::name('system_task')->where('id','in',$out_id_arr)->update(['status'=>3]);
            }
            if($max_crontab_arr){
                $max_id_arr = array_column($max_crontab_arr,'id');
                Db::name('system_task')->where('id','in',$max_id_arr)->update(['status'=>2]);
            }
            if($exe_crontab_arr){
                $chan = new Coroutine\Channel(count($exe_crontab_arr));
                $port = Config::get('swoole.server.port');


                foreach($exe_crontab_arr as $key => $crontab){
                    $log = [
                        'crontab_id'   => $crontab['id'],
                        'content'      => '',
                        'status'       => '', // 暂时不知道
                        'create_datetime' => Date('Y-m-d H:i:s')
                    ];
                    $crontab['log_id'] = Db::name("system_task_log")->insertGetId($log);
                    // 
                    go(function () use ($chan,$crontab,$port) {
                        $curl = new Curl();
                        $curl->setOpt(CURLOPT_TIMEOUT,50);
                        $curl->get('http://0.0.0.0:'.$port.$crontab['content']);
                        // $res = $curl->response;
                        $curl->close();
                        if(is_string($curl->response)){
                            $chan->push([$crontab['log_id'] => ['msg'=>'执行完毕','data'=>$curl->response,'update_time'=>time()]]);
                        }else{
                            $chan->push([$crontab['log_id'] => ['msg'=>'执行超时或异常','data'=>$curl->response,'update_time'=>time()]]);
                        }
                        
                    });
                }
                $result = [];
                $log_res_arr = [];
                for ($i = 0; $i < count($exe_crontab_arr); $i++)
                {
                    $result += $chan->pop();
                }
                
                foreach($result as $log_id => $res_data){
                    $update_datetime = Date('Y-m-d H:i:s',$res_data['update_time']);
                    if($res_data['msg'] == '执行完毕'){
                        $res = json_decode($res_data['data'],true);
                        if(isset($res['code'] ) && $res['code'] == 200){
                            array_push($log_res_arr,['id'=>$log_id,'status'=>'success','content'=>'成功','update_datetime'=>$update_datetime]);
                        }else{
                            $fail_msg = isset($res['msg']) ? $res['msg'] : '无msg返回,未知错误';
                            array_push($log_res_arr,['id'=>$log_id,'status'=>'failure','content'=>$fail_msg,'update_datetime'=>$update_datetime]);
                        }
                    }else{
                        array_push($log_res_arr,['id'=>$log_id,'status'=>'failure','content'=>$res_data['msg'],'update_datetime'=>$update_datetime]);
                    }
                }
                $model = new SystemTaskLog();
                $model->saveAll($log_res_arr);
            }
        });
        // 指令输出
        $output->writeln('Execute completed!'.Date('Y-m-d H:i:s'));
    }
}

在linux中设置该定时

crontab -e

* * * * * /www/server/php/73/bin/php /www/wwwroot/abc.com/think task  >> /www/server/cron/abc.com.log 2>&1

设置定时任务

查看定时结果

秒级定时

因为我们用的 cron-expression 是分钟级的,如果执行到秒级,其实是做不到的。

目前是任务里做一分钟的分割,以下做了简单粗暴的处理,当然如果项目里面有很多秒级任务,建议自己优化,或者其他方案

    public function time20s()
    {

        $this->doOnce();
        //在指定的时间后执行函数,20秒后执行
        \Swoole\Timer::after(20000, function() use ($url) {
            $this->doOnce();
            \Swoole\Timer::after(20000, function() use ($url) {
                  $this->doOnce();
            });
        });

    }

注意点

1、设置50秒超时,防止任务执行过长,所有任务的执行结果都得不到

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值