首先介紹一下我遇到過的,我的以爲奇葩的極其不方便的定時任務方式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 |
|
|
|
|
|