Use YII2 to build a timed task management background

First of all, let me introduce the timed task method that I have encountered, which I personally think is extremely inconvenient.

Whenever there is a timed task linuxrequirement crontab, register a task under

*/5 * * * * wget --spider "http://xxxxx.com/index.php?m=Kf&c=Task&a=recommendTasks"
*/2 * * * * wget --spider "http://xxxxx.com/index.php?m=Kf&c=Task&a=batchOneBuyCodesa"
*/5 * * * * wget --spider "http://xxxxx.com/index.php?m=Kf&c=Task&a=bathCardtradesd"
*/1 * * * * wget --spider "http://xxxxx.com/index.php?m=Kf&c=Task&a=pushg"

I don’t know if there are any big brothers lying down. I hope you will not do timed tasks like this in the future after seeing my implementation method. Of course, mine will not be the best.

What's wrong with timing tasks in this way?

  1. It's obvious that I don't know what this kind of ghost link is. If I want to stop, I don't dare to stop being afraid of taking the blame. Over time, I will throw it on it.
  2. The HTTP request method triggers the task, and when there are many tasks, the resources of the webserver are occupied ( 如果是以cli模式触发就算了,当我没说)
  3. It is impossible to record the running status of the task, for example: whether the running is successful, how long does it take to run once ( 你千万别跟我说在每个任务记录个里日志啥的好吧)

I will start my implementation process around how to solve the above three problems

  • Create a table dedicated to managing scheduled tasks
CREATE TABLE `tb_crontab` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL COMMENT '定时任务名称',
  `route` varchar(50) NOT NULL COMMENT '任务路由',
  `crontab_str` varchar(50) NOT NULL COMMENT 'crontab格式',
  `switch` tinyint(1) NOT NULL DEFAULT '0' COMMENT '任务开关 0关闭 1开启',
  `status` tinyint(1) DEFAULT '0' COMMENT '任务运行状态 0正常 1任务报错',
  `last_rundate` datetime DEFAULT NULL COMMENT '任务上次运行时间',
  `next_rundate` datetime DEFAULT NULL COMMENT '任务下次运行时间',
  `execmemory` decimal(9,2) NOT NULL DEFAULT '0.00' COMMENT '任务执行消耗内存(单位/byte)',
  `exectime` decimal(9,2) NOT NULL DEFAULT '0.00' COMMENT '任务执行消耗时间',
  PRIMARY KEY (`id`)
) 
  • All tasks are scheduled through an entry method
* * * * * cd /server/webroot/yii-project/ && php yii crontab/index
  • Implement task scheduling controllercommands/CrontabController.php
<?php
namespace app\commands;

use Yii;
use yii\console\Controller;
use yii\console\ExitCode;
use app\common\models\Crontab;

/**
 * 定时任务调度控制器
 * @author jlb
 */
class CrontabController extends Controller
{

    /**
     * 定时任务入口
     * @return int Exit code
     */
    public function actionIndex()
    {
    	$crontab = Crontab::findAll(['switch' => 1]);
    	$tasks = [];

    	foreach ($crontab as $task) {

    		// 第一次运行,先计算下次运行时间
    		if (!$task->next_rundate) {
    			$task->next_rundate = $task->getNextRunDate();
    			$task->save(false);
    			continue;
    		}

    		// 判断运行时间到了没
    		if ($task->next_rundate <= date('Y-m-d H:i:s')) {
                $tasks[] = $task;
    		}
    	}

        $this->executeTask($tasks);

        return ExitCode::OK;
    }
    
    /**
     * @param  array $tasks 任务列表
     * @author jlb
     */
    public function executeTask(array $tasks)
    {

        $pool = [];
        $startExectime = $this->getCurrentTime();

        foreach ($tasks as $task) {
            
            $pool[] = proc_open("php yii $task->route", [], $pipe);
        }

        // 回收子进程
        while (count($pool)) {
            foreach ($pool as $i => $result) {
                $etat = proc_get_status($result);
                if($etat['running'] == FALSE) {
                    proc_close($result);
                    unset($pool[$i]);
                    # 记录任务状态
                    $tasks[$i]->exectime     = round($this->getCurrentTime() - $startExectime, 2);
                    $tasks[$i]->last_rundate = date('Y-m-d H:i');
                    $tasks[$i]->next_rundate = $tasks[$i]->getNextRunDate();
                    $tasks[$i]->status       = 0;
                    // 任务出错
                    if ($etat['exitcode'] !== ExitCode::OK) {
                        $tasks[$i]->status = 1;
                    }   

                    $tasks[$i]->save(false);
                }
            }
        }
    }

    private function getCurrentTime ()  {  
        list ($msec, $sec) = explode(" ", microtime());  
        return (float)$msec + (float)$sec;  
    }
   
}

  • Implement the crontab model, common/models/Crontab.phpif not , create it yourself
<?php
namespace app\common\models;

use Yii;
use app\common\helpers\CronParser;

/**
 * 定时任务模型
 * @author jlb
 */
class Crontab extends \yii\db\ActiveRecord
{

	/**
	 * switch字段的文字映射
	 * @var array
	 */
	private $switchTextMap = [
		0 => '关闭',
		1 => '开启',
	];

	/**
	 * status字段的文字映射
	 * @var array
	 */
	private $statusTextMap = [
		0 => '正常',
		1 => '任务保存',
	];

    public static function getDb()
    {
       #注意!!!替换成自己的数据库配置组件名称
        return Yii::$app->tfbmall;
    }
    /**
     * 获取switch字段对应的文字
     * @author jlb
     * @return ''|string
     */
    public function getSwitchText()
    {
    	if(!isset($this->switchTextMap[$this->switch])) {
    		return '';
    	}
    	return $this->switchTextMap[$this->switch];
    }

    /**
     * 获取status字段对应的文字
     * @author jlb
     * @return ''|string
     */
    public function getStatusText()
    {
    	if(!isset($this->statusTextMap[$this->status])) {
    		return '';
    	}
    	return $this->statusTextMap[$this->status];
    }

    /**
     * 计算下次运行时间
     * @author jlb
     */
    public function getNextRunDate()
    {
    	if (!CronParser::check($this->crontab_str)) {
    		throw new \Exception("格式校验失败: {$this->crontab_str}", 1);
    	}
    	return CronParser::formatToDate($this->crontab_str, 1)[0];
    }

}
  • A crontab format tool parsing classcommon/helpers/CronParser.php
<?php
namespace app\common\helpers;

/**
 * crontab格式解析工具类
 * @author jlb <[email protected]>
 */
class CronParser
{

    protected static $weekMap = [
        0 => 'Sunday',
        1 => 'Monday',
        2 => 'Tuesday',
        3 => 'Wednesday',
        4 => 'Thursday',
        5 => 'Friday',
        6 => 'Saturday',
    ];

    /**
     * 检查crontab格式是否支持
     * @param  string $cronstr 
     * @return boolean true|false
     */
    public static function check($cronstr)
    {
        $cronstr = trim($cronstr);

        if (count(preg_split('#\s+#', $cronstr)) !== 5) {
            return false;
        }

        $reg = '#^(\*(/\d+)?|\d+([,\d\-]+)?)\s+(\*(/\d+)?|\d+([,\d\-]+)?)\s+(\*(/\d+)?|\d+([,\d\-]+)?)\s+(\*(/\d+)?|\d+([,\d\-]+)?)\s+(\*(/\d+)?|\d+([,\d\-]+)?)$#';
        if (!preg_match($reg, $cronstr)) {
            return false;
        }

        return true;
    }

    /**
     * 格式化crontab格式字符串
     * @param  string $cronstr
     * @param  interge $maxSize 设置返回符合条件的时间数量, 默认为1
     * @return array 返回符合格式的时间
     */
    public static function formatToDate($cronstr, $maxSize = 1) 
    {

        if (!static::check($cronstr)) {
            throw new \Exception("格式错误: $cronstr", 1);
        }

        $tags = preg_split('#\s+#', $cronstr);

        $crons = [
            'minutes' => static::parseTag($tags[0], 0, 59), //分钟
            'hours'   => static::parseTag($tags[1], 0, 23), //小时
            'day'     => static::parseTag($tags[2], 1, 31), //一个月中的第几天
            'month'   => static::parseTag($tags[3], 1, 12), //月份
            'week'    => static::parseTag($tags[4], 0, 6), // 星期
        ];

        $crons['week'] = array_map(function($item){
            return static::$weekMap[$item];
        }, $crons['week']);

        $nowtime = strtotime(date('Y-m-d H:i'));
        $today = getdate();
        $dates = [];
        foreach ($crons['month'] as $month) {
            // 获取单月最大天数
            $maxDay = cal_days_in_month(CAL_GREGORIAN, $month, date('Y'));
            foreach ($crons['day'] as $day) {
                if ($day > $maxDay) {
                    break;
                }
                foreach ($crons['hours'] as $hours) {
                    foreach ($crons['minutes'] as $minutes) {
                        $i = mktime($hours, $minutes, 0, $month, $day);
                        if ($nowtime > $i) {
                            continue;
                        }
                        $date = getdate($i);

                        // 解析是第几天
                        if ($tags[2] != '*' && in_array($date['mday'], $crons['day'])) {
                            $dates[] = date('Y-m-d H:i', $i);
                        }

                        // 解析星期几
                        if ($tags[4] != '*' && in_array($date['weekday'], $crons['week'])) {
                            $dates[] = date('Y-m-d H:i', $i);
                        }

                        // 天与星期几
                        if ($tags[2] == '*' && $tags[4] == '*') {
                            $dates[] = date('Y-m-d H:i', $i);
                        }

                        
                        if (isset($dates) && count($dates) == $maxSize) {
                            break 4;
                        }
                    }
                }
            }
        }

        return array_unique($dates);
    }
    /**
     * 解析元素
     * @param  string $tag  元素标签
     * @param  integer $tmin 最小值
     * @param  integer $tmax 最大值
     * @throws \Exception
     */
    protected static function parseTag($tag, $tmin, $tmax)
    {
        if ($tag == '*') {
            return range($tmin, $tmax);
        }

        $step = 1;
        $dateList = [];

        if (false !== strpos($tag, '/')) {
            $tmp = explode('/', $tag);
            $step = isset($tmp[1]) ? $tmp[1] : 1;
            
            $dateList = range($tmin, $tmax, $step);
        }
        else if (false !== strpos($tag, '-')) {
            list($min, $max) = explode('-', $tag);
            if ($min > $max) {
                list($min, $max) = [$max, $min];
            }
            $dateList = range($min, $max, $step);
        }
        else if (false !== strpos($tag, ',')) {
            $dateList = explode(',', $tag);
        }
        else {
            $dateList = array($tag);
        }

        // 越界判断
        foreach ($dateList as $num) {
            if ($num < $tmin || $num > $tmax) {
                throw new \Exception('数值越界');
            }
        }

        sort($dateList);

        return $dateList;
    }
}

you're done

Create a method for testing commands/tasks/TestController.php

<?php
namespace app\commands\tasks;

use Yii;
use yii\console\Controller;
use yii\console\ExitCode;

class TestController extends Controller
{
    /**
     * @return int Exit code
     */
    public function actionIndex()
    {
		sleep(1);
        echo "我是index方法\n";
        return ExitCode::OK;
    }

    /**
     * @return int Exit code
     */
    public function actionTest()
    {
		sleep(2);
        echo "我是test方法\n";
        return ExitCode::OK;
    }

}

Remember the crontab table created at the beginning, manually add tasks to the table as follows
image.png

Enter the yii root directory and run php yii crontab/indexto see the effect

Finally, I offer the addition, deletion, modification, and checking of the timing task management interface that I have done.

I'll bother you to imitate this piece by yourself.

image.png

 

run once a minute with crontab

* * * * * cd /yii-project/ && php yii crontab/index

The old CronParser class is not perfect and has bugs, so you may find the latest crontab parsing class
attached . My solution only supports single-server deployment. If there are too many scheduled tasks and the single-machine is not enough, I need to cluster it. There is a plan, but it has not been put into practice yet. Whether it is necessary to mention it depends on your feedback and needs.

 
 
G
M
T
 
 
Detect languageAfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBengaliBosnianBulgarianCatalanCebuanoChichewaChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDutchEnglishEsperantoEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekGujaratiHaitian CreoleHausaHebrewHindiHmongHungarianIcelandicIgboIndonesianIrishItalianJapaneseJavaneseKannadaKazakhKhmerKoreanLaoLatinLatvianLithuanianMacedonianMalagasyMalayMalayalamMalteseMaoriMarathiMongolianMyanmar (Burmese)NepaliNorwegianPersianPolishPortuguesePunjabiRomanianRussianSerbianSesothoSinhalaSlovakSlovenianSomaliSpanishSundaneseSwahiliSwedishTajikTamilTeluguThaiTurkishUkrainianUrduUzbekVietnameseWelshYiddishYorubaZulu
 
AfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBengaliBosnianBulgarianCatalanCebuanoChichewaChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDutchEnglishEsperantoEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekGujaratiHaitian CreoleHausaHebrewHindiHmongHungarianIcelandicIgboIndonesianIrishItalianJapaneseJavaneseKannadaKazakhKhmerKoreanLaoLatinLatvianLithuanianMacedonianMalagasyMalayMalayalamMalteseMaoriMarathiMongolianMyanmar (Burmese)NepaliNorwegianPersianPolishPortuguesePunjabiRomanianRussianSerbianSesothoSinhalaSlovakSlovenianSomaliSpanishSundaneseSwahiliSwedishTajikTamilTeluguThaiTurkishUkrainianUrduUzbekVietnameseWelshYiddishYorubaZulu
 
 
 
 
 
 
 
 
 
Text-to-speech function is limited to 200 characters
 
 
Options : History : Feedback : Donate Close

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325310343&siteId=291194637