演進(jìn):新型計(jì)劃任務(wù)續(xù)篇

2018-11-21 21:20 更新

計(jì)算機(jī)科學(xué)中的任何問(wèn)題都可以用另外的間接層解決,但是這通常會(huì)引發(fā)另一個(gè)問(wèn)題。 -- David Wheeler

2.15.1 新型計(jì)劃任務(wù)回顧

 [1.31]-新型計(jì)劃任務(wù):以接口形式實(shí)現(xiàn)的計(jì)劃任務(wù) 一章中,我們討論了PhalApi中對(duì)計(jì)劃任務(wù)的設(shè)計(jì)和底層實(shí)現(xiàn)。

但對(duì)于很多應(yīng)用,很多項(xiàng)目,或者很多同學(xué)來(lái)說(shuō),仍然比較廣泛,不能直接使用。
這一章則專門(mén)為此而進(jìn)行演進(jìn),并提供最終可用的計(jì)劃任務(wù)調(diào)度,同時(shí)我們也會(huì)闡明如何進(jìn)行擴(kuò)展定制。

也就是說(shuō),這一章將提供Task擴(kuò)展類庫(kù)的統(tǒng)一調(diào)度方式,以便在啟動(dòng)crontab任務(wù)后,可以通過(guò)數(shù)據(jù)庫(kù)簡(jiǎn)單配置,即可執(zhí)行各種任務(wù)。

2.15.2 最終調(diào)度的方式:crontab

出于對(duì)業(yè)務(wù)的考慮,我們首先需要明確此crontab調(diào)度方式所支持的功能,它應(yīng)該包括但不限于:

  • 1、通過(guò)簡(jiǎn)單的數(shù)據(jù)庫(kù)配置,即可啟動(dòng)一個(gè)新的任務(wù)
  • 2、具備循環(huán)調(diào)度的能力,并能初步防止并發(fā)調(diào)度
  • 3、可以對(duì)異常的任務(wù)進(jìn)行修復(fù)
  • 4、優(yōu)先執(zhí)行太遠(yuǎn)未執(zhí)行的任務(wù)
  • 5、支持本地和遠(yuǎn)程兩種調(diào)度方式、三種MQ類型,以及擴(kuò)展的能力

2.15.3 核心時(shí)序圖與分層

在原來(lái)的時(shí)序圖基礎(chǔ)上,我們可以進(jìn)行演進(jìn)的設(shè)計(jì),追加了統(tǒng)一的調(diào)度后如下所示:
a pic

通過(guò)上面詳細(xì)的時(shí)序圖,我們可以發(fā)現(xiàn)里面的設(shè)計(jì)是出于這樣的分層考慮:

序號(hào)關(guān)鍵操作說(shuō)明如何使用
1啟動(dòng)腳本crontab.php操作crontab執(zhí)行的腳本客戶端可以進(jìn)行必要的初始化工作
2進(jìn)程級(jí)Task_Progress::run()根據(jù)進(jìn)程配置的數(shù)據(jù)庫(kù)表,進(jìn)行循環(huán)調(diào)度不需要改動(dòng),直接使用
3觸發(fā)器Task_Trigger::fire()進(jìn)行計(jì)劃任務(wù)調(diào)度的上下文環(huán)境,用于指定runner和mq類型客戶端也可進(jìn)行定制擴(kuò)展,進(jìn)行必要的操作
4MQ消費(fèi)與調(diào)度Task_MQ::pop()和Task_Runner::go()不斷消費(fèi)MQ隊(duì)列,并依次進(jìn)行調(diào)度不需要改動(dòng),直接使用,也可擴(kuò)展
5計(jì)劃任務(wù)服務(wù)PhalApi_Api::doSth()執(zhí)行計(jì)劃任務(wù)服務(wù)由客戶端按接口形式實(shí)現(xiàn)

雖然上面的層級(jí),初看起來(lái)有點(diǎn)多,但我們?cè)俅悟?yàn)證了計(jì)算機(jī)那個(gè)偉大的定論:計(jì)算機(jī)的任何問(wèn)題都可以通過(guò)一個(gè)中間層來(lái)解決。

由此看出,上面的層級(jí)其實(shí)相當(dāng)于:

客戶端初始化 --> 直接使用 --> 自由組合與操作 --> 直接使用 --> 任務(wù)服務(wù)實(shí)現(xiàn)

2.16.4 進(jìn)程配置的數(shù)據(jù)庫(kù)表設(shè)計(jì)

CREATE TABLE `phalapi_task_progress` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `title` varchar(200) DEFAULT '' COMMENT '任務(wù)標(biāo)題',
  `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 '是否啟動(dòng),1啟動(dòng),0禁止',
  `result` varchar(255) DEFAULT '' COMMENT '運(yùn)行的結(jié)果,以json格式保存',
  `state` tinyint(1) DEFAULT '0' COMMENT '進(jìn)程狀態(tài),0空閑,1運(yùn)行中,-1異常退出',
  `last_fire_time` int(11) DEFAULT '0' COMMENT '上一次運(yùn)行時(shí)間',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

對(duì)此表的關(guān)鍵字段說(shuō)明如下:

字段說(shuō)明示例
trigger_class觸發(fā)器的類名須實(shí)現(xiàn)Task_Progress_Trigger::fire($params)接口
fire_params觸發(fā)器的參數(shù)加傳給Task_Progress_Trigger::fire()函數(shù)的參數(shù),格式為:service&MQ類名&runner類名
interval_time執(zhí)行間隔單位為秒
enable是否啟動(dòng)此字段禁止時(shí),將不再執(zhí)行
state進(jìn)程狀態(tài)當(dāng)此狀態(tài)一直為異?;蛘哌\(yùn)行且超過(guò)1天時(shí),系統(tǒng)會(huì)進(jìn)行修復(fù),即重置為空閑狀態(tài)

其中,對(duì)于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:使用默認(rèn)的Runner
//fire_params=Task_Demo.DoSth&Task_MQ_DB
$mq = new Task_MQ_DB();
$runner = new Task_Runner_Local($mq);  //默認(rèn)使用本地Runner
$runner->go('Task_Demo.DoSth');

//示例3:使用默認(rèn)的MQ和默認(rèn)的Runner
//fire_params=Task_Demo.DoSth
$mq = new Task_MQ_Redis(); //默認(rèn)使用redis的MQ
$runner = new Task_Runner_Local($mq);  //默認(rèn)使用本地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');

2.15.5 運(yùn)行效果

最終的效果就是,我們通過(guò)這樣兩行簡(jiǎn)單的代碼,即可實(shí)現(xiàn)一系列復(fù)雜的任務(wù)調(diào)度:

$progress = new Task_Progress();
$progress->run();

讓我們來(lái)看下這樣設(shè)計(jì)的運(yùn)行效果吧!看下這兩行代碼背后所產(chǎn)生的魔力。

首先,我們先添加兩條計(jì)劃任務(wù):

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', '');

最后,生成單元測(cè)試:

<?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)

查看對(duì)比一下數(shù)據(jù)庫(kù),目前發(fā)現(xiàn)運(yùn)行良好!

提交代碼,保存文檔,收工睡覺(jué)!

2.15.6 演進(jìn)的樂(lè)趣

得益于前期良好的設(shè)計(jì)以及底層支持,我們發(fā)現(xiàn),在提供這樣一種統(tǒng)一的調(diào)度方式是非常方便的。

不僅如此,如果你明白了其中的設(shè)計(jì),需要進(jìn)行定制和擴(kuò)展也是非常方便的。也就是說(shuō),我們不僅提供了一種具體實(shí)際可用的方式,也提供了廣闊自由的擴(kuò)展空間。具體與抽象,兩者仍可得。

然而,這一切不僅依賴于良好的設(shè)計(jì),還依賴于測(cè)試驅(qū)動(dòng)開(kāi)發(fā)下的浮現(xiàn)式設(shè)計(jì)。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)