演進:新型計劃任務續(xù)篇

2018-11-21 21:20 更新

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

2.15.1 新型計劃任務回顧

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

但對于很多應用,很多項目,或者很多同學來說,仍然比較廣泛,不能直接使用。
這一章則專門為此而進行演進,并提供最終可用的計劃任務調(diào)度,同時我們也會闡明如何進行擴展定制。

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

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

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

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

2.15.3 核心時序圖與分層

在原來的時序圖基礎上,我們可以進行演進的設計,追加了統(tǒng)一的調(diào)度后如下所示:
a pic

通過上面詳細的時序圖,我們可以發(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類型客戶端也可進行定制擴展,進行必要的操作
4MQ消費與調(diào)度Task_MQ::pop()和Task_Runner::go()不斷消費MQ隊列,并依次進行調(diào)度不需要改動,直接使用,也可擴展
5計劃任務服務PhalApi_Api::doSth()執(zhí)行計劃任務服務由客戶端按接口形式實現(xiàn)

雖然上面的層級,初看起來有點多,但我們再次驗證了計算機那個偉大的定論:計算機的任何問題都可以通過一個中間層來解決。

由此看出,上面的層級其實相當于:

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

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

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

2.15.5 運行效果

最終的效果就是,我們通過這樣兩行簡單的代碼,即可實現(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)運行良好!

提交代碼,保存文檔,收工睡覺!

2.15.6 演進的樂趣

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

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

然而,這一切不僅依賴于良好的設計,還依賴于測試驅(qū)動開發(fā)下的浮現(xiàn)式設計。

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號