首先介绍一下我遇到过的,我的以为奇葩的极其不方便的定时任务方式php
每当有一个定时任务需求就在linux
下crontab
中注册一个任务linux
*/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"
不知道有不有大兄弟躺枪了,但愿你看了个人实现方式后,之后不要这么搞定时任务了,固然个人也不会是最好了,别钻牛角尖git
这种方式的定时任务有什么问题?web
若是是以cli模式触发就算了,当我没说
)你千万别跟我说在每一个任务记录个里日志啥的好吧
)我将围绕如何解决以上三个问题来展开个人实现过程数据库
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`) )
* * * * * cd /server/webroot/yii-project/ && php yii crontab/index
commands/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; } }
common/models/Crontab.php
没有则本身建立<?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]; } }
common/helpers/CronParser.php
<?php namespace app\common\helpers; /** * crontab格式解析工具类 * @author jlb <497012571@qq.com> */ 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; } }
大功告成服务器
建立一个用于测试的方法吧 commands/tasks/TestController.php
app
<?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; } }
还记得一开始就建立好的crontab表吗,手动在表添加任务以下
yii
进入yii根目录运行
php yii crontab/index
便可看到效果ide
最后祭出我作好的的增删改查定时任务管理界面工具
这一块就劳烦你本身动动手仿照作出来吧
用crontab 一分钟运行一次
* * * * * cd /yii-project/ && php yii crontab/index
旧的CronParser类不完善有BUG,因此附上最新的 crontab解析类
你们也许发现了,我这种方案只支持单服务器部署,若是定时任务太多,单机不够的状况下要作下集群,我也是有个方案,可是还没实际运用,是否有必要提上来,须要看你们的反馈与需求
Detect languageAfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBengaliBosnianBulgarianCatalanCebuanoChichewaChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDutchEnglishEsperantoEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekGujaratiHaitian CreoleHausaHebrewHindiHmongHungarianIcelandicIgboIndonesianIrishItalianJapaneseJavaneseKannadaKazakhKhmerKoreanLaoLatinLatvianLithuanianMacedonianMalagasyMalayMalayalamMalteseMaoriMarathiMongolianMyanmar (Burmese)NepaliNorwegianPersianPolishPortuguesePunjabiRomanianRussianSerbianSesothoSinhalaSlovakSlovenianSomaliSpanishSundaneseSwahiliSwedishTajikTamilTeluguThaiTurkishUkrainianUrduUzbekVietnameseWelshYiddishYorubaZulu |
|
AfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBengaliBosnianBulgarianCatalanCebuanoChichewaChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDutchEnglishEsperantoEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekGujaratiHaitian CreoleHausaHebrewHindiHmongHungarianIcelandicIgboIndonesianIrishItalianJapaneseJavaneseKannadaKazakhKhmerKoreanLaoLatinLatvianLithuanianMacedonianMalagasyMalayMalayalamMalteseMaoriMarathiMongolianMyanmar (Burmese)NepaliNorwegianPersianPolishPortuguesePunjabiRomanianRussianSerbianSesothoSinhalaSlovakSlovenianSomaliSpanishSundaneseSwahiliSwedishTajikTamilTeluguThaiTurkishUkrainianUrduUzbekVietnameseWelshYiddishYorubaZulu |
|
|
|
|
|