計算機科學中的任何問題都可以用另外的間接層解決,但是這通常會引發(fā)另一個問題。 -- David Wheeler
在 [1.31]-新型計劃任務:以接口形式實現(xiàn)的計劃任務 一章中,我們討論了PhalApi中對計劃任務的設計和底層實現(xiàn)。
但對于很多應用,很多項目,或者很多同學來說,仍然比較廣泛,不能直接使用。
這一章則專門為此而進行演進,并提供最終可用的計劃任務調(diào)度,同時我們也會闡明如何進行擴展定制。
也就是說,這一章將提供Task擴展類庫的統(tǒng)一調(diào)度方式,以便在啟動crontab任務后,可以通過數(shù)據(jù)庫簡單配置,即可執(zhí)行各種任務。
出于對業(yè)務的考慮,我們首先需要明確此crontab調(diào)度方式所支持的功能,它應該包括但不限于:
在原來的時序圖基礎上,我們可以進行演進的設計,追加了統(tǒng)一的調(diào)度后如下所示:
通過上面詳細的時序圖,我們可以發(fā)現(xiàn)里面的設計是出于這樣的分層考慮:
序號 | 層 | 關鍵操作 | 說明 | 如何使用 |
---|---|---|---|---|
1 | 啟動腳本 | crontab.php | 操作crontab執(zhí)行的腳本 | 客戶端可以進行必要的初始化工作 |
2 | 進程級 | Task_Progress::run() | 根據(jù)進程配置的數(shù)據(jù)庫表,進行循環(huán)調(diào)度 | 不需要改動,直接使用 |
3 | 觸發(fā)器 | Task_Trigger::fire() | 進行計劃任務調(diào)度的上下文環(huán)境,用于指定runner和mq類型 | 客戶端也可進行定制擴展,進行必要的操作 |
4 | MQ消費與調(diào)度 | Task_MQ::pop()和Task_Runner::go() | 不斷消費MQ隊列,并依次進行調(diào)度 | 不需要改動,直接使用,也可擴展 |
5 | 計劃任務服務 | PhalApi_Api::doSth() | 執(zhí)行計劃任務服務 | 由客戶端按接口形式實現(xiàn) |
雖然上面的層級,初看起來有點多,但我們再次驗證了計算機那個偉大的定論:計算機的任何問題都可以通過一個中間層來解決。
由此看出,上面的層級其實相當于:
客戶端初始化 --> 直接使用 --> 自由組合與操作 --> 直接使用 --> 任務服務實現(xiàn)
CREATE TABLE `phalapi_task_progress` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`title` varchar(200) DEFAULT '' COMMENT '任務標題',
`trigger_class` varchar(50) DEFAULT '' COMMENT '觸發(fā)器類名',
`fire_params` varchar(255) DEFAULT '' COMMENT '需要傳遞的參數(shù),格式自定',
`interval_time` int(11) DEFAULT '0' COMMENT '執(zhí)行間隔,單位:秒',
`enable` tinyint(1) DEFAULT '1' COMMENT '是否啟動,1啟動,0禁止',
`result` varchar(255) DEFAULT '' COMMENT '運行的結(jié)果,以json格式保存',
`state` tinyint(1) DEFAULT '0' COMMENT '進程狀態(tài),0空閑,1運行中,-1異常退出',
`last_fire_time` int(11) DEFAULT '0' COMMENT '上一次運行時間',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
對此表的關鍵字段說明如下:
字段 | 說明 | 示例 |
---|---|---|
trigger_class | 觸發(fā)器的類名 | 須實現(xiàn)Task_Progress_Trigger::fire($params)接口 |
fire_params | 觸發(fā)器的參數(shù) | 加傳給Task_Progress_Trigger::fire()函數(shù)的參數(shù),格式為:service&MQ類名&runner類名 |
interval_time | 執(zhí)行間隔 | 單位為秒 |
enable | 是否啟動 | 此字段禁止時,將不再執(zhí)行 |
state | 進程狀態(tài) | 當此狀態(tài)一直為異?;蛘哌\行且超過1天時,系統(tǒng)會進行修復,即重置為空閑狀態(tài) |
其中,對于fire_params參數(shù),MQ類名和runner類名可選,以下是一些示例:
//示例1:完整的配置
//fire_params=Task_Demo.DoSth&Task_MQ_DB&Task_Runner_Local
$mq = new Task_MQ_DB();
$runner = new Task_Runner_Local($mq);
$runner->go('Task_Demo.DoSth');
//示例2:使用默認的Runner
//fire_params=Task_Demo.DoSth&Task_MQ_DB
$mq = new Task_MQ_DB();
$runner = new Task_Runner_Local($mq); //默認使用本地Runner
$runner->go('Task_Demo.DoSth');
//示例3:使用默認的MQ和默認的Runner
//fire_params=Task_Demo.DoSth
$mq = new Task_MQ_Redis(); //默認使用redis的MQ
$runner = new Task_Runner_Local($mq); //默認使用本地Runner
$runner->go('Task_Demo.DoSth');
//示例4:使用自定義的MQ和Runner
//fire_params=Task_Demo.DoSth&My_MQ&My_Runner
class My_MQ implements Task_MQ {
// ...
}
class My_Runner extends Task_Runner {
// ...
}
$mq = new My_MQ();
$runner = new My_Runner($mq);
$runner->go('Task_Demo.DoSth');
最終的效果就是,我們通過這樣兩行簡單的代碼,即可實現(xiàn)一系列復雜的任務調(diào)度:
$progress = new Task_Progress();
$progress->run();
讓我們來看下這樣設計的運行效果吧!看下這兩行代碼背后所產(chǎn)生的魔力。
首先,我們先添加兩條計劃任務:
INSERT INTO `phalapi_task_progress` VALUES ('1', 'test demo', 'Task_Progress_Trigger_Common', 'Task_Demo.DoSth&Task_MQ_File&Task_Runner_Local', '300', '1', '', '0', '0');
INSERT INTO `phalapi_task_progress` VALUES ('2', 'test ok', 'Task_Progress_Trigger_Common', 'Default.Index&Task_MQ_DB&Task_Runner_Local', '100', '1', '', '0', '0');
然后,偽造一些MQ:
INSERT INTO `phalapi_task_mq_0` VALUES ('8', 'Default.Index', '', '0', '');
最后,生成單元測試:
<?php
class PhpUnderControl_TaskProgress_Test extends PHPUnit_Framework_TestCase
{
public $taskProgress;
protected function setUp()
{
parent::setUp();
$this->taskProgress = new Task_Progress();
}
/**
* @group testRun
*/
public function testRun()
{
$rs = $this->taskProgress->run();
}
}
并執(zhí)行之:
$ phpunit ./Task_Progress_Test.php
[1 - 0.06666s]SELECT id, title FROM phalapi_task_progress WHERE (state != ?) AND (last_fire_time < ?) AND (enable = ?) ORDER BY last_fire_time ASC; -- 0, 1431965153, 1<br>
[2 - 0.07002s]SELECT id, title, trigger_class, fire_params FROM phalapi_task_progress WHERE (state = 0) AND (interval_time + last_fire_time < ?) AND (enable = ?); -- 1432051553, 1<br>
[3 - 0.06549s]SELECT enable, state FROM phalapi_task_progress WHERE (id = '1');<br>
[4 - 0.07432s]UPDATE phalapi_task_progress SET state = 1 WHERE (id = '1');<br>
[5 - 0.06469s]UPDATE phalapi_task_progress SET result = '{\"total\":0,\"fail\":0}', state = 0, last_fire_time = 1432051553 WHERE (id = '1');<br>
[6 - 0.06746s]SELECT enable, state FROM phalapi_task_progress WHERE (id = '2');<br>
[7 - 0.07043s]UPDATE phalapi_task_progress SET state = 1 WHERE (id = '2');<br>
[8 - 0.06673s]SELECT id, params FROM phalapi_task_mq_0 WHERE (service = 'Default.Index') ORDER BY id ASC LIMIT 0,10;<br>
[9 - 0.48185s]DELETE FROM phalapi_task_mq_0 WHERE (id IN ('8'));<br>
[10 - 0.06514s]SELECT id, params FROM phalapi_task_mq_0 WHERE (service = 'Default.Index') ORDER BY id ASC LIMIT 0,10;<br>
[11 - 0.50694s]UPDATE phalapi_task_progress SET result = '{\"total\":1,\"fail\":0}', state = 0, last_fire_time = 1432051553 WHERE (id = '2');<br>
Time: 1.98 seconds, Memory: 6.50Mb
OK (1 test, 0 assertions)
查看對比一下數(shù)據(jù)庫,目前發(fā)現(xiàn)運行良好!
提交代碼,保存文檔,收工睡覺!
得益于前期良好的設計以及底層支持,我們發(fā)現(xiàn),在提供這樣一種統(tǒng)一的調(diào)度方式是非常方便的。
不僅如此,如果你明白了其中的設計,需要進行定制和擴展也是非常方便的。也就是說,我們不僅提供了一種具體實際可用的方式,也提供了廣闊自由的擴展空間。具體與抽象,兩者仍可得。
然而,這一切不僅依賴于良好的設計,還依賴于測試驅(qū)動開發(fā)下的浮現(xiàn)式設計。
更多建議: