拒絕分散拘哨,試試任務(wù)引擎

前言:

大家做后端開發(fā),一定都遇到過很多需求需要依靠定時任務(wù)去完成信峻,可是不同的部門不同的開發(fā)會寫在不同的項目中倦青。定時任務(wù)也會越來越多越來越不好管理。既然大家的需求都是定時完成一些任務(wù)盹舞,只是任務(wù)內(nèi)容不同产镐,那我們就可以將管理任務(wù)執(zhí)行任務(wù)這部分功能抽象出來做成一個服務(wù)。我稱這樣的服務(wù)為任務(wù)引擎

流程

流程大致如上圖矾策,我們可以來拆解一下磷账。

  1. 首先需要做任務(wù),那么就必須得有任務(wù)贾虽。寫入任務(wù)其實也就是寫表逃糟。這個應(yīng)該需要封裝成接口對外提供
  2. 根據(jù)任務(wù)狀態(tài)獲取出100條數(shù)據(jù)
  3. 將取出的數(shù)據(jù)遍歷循環(huán)
  4. 對應(yīng)單條數(shù)據(jù)的type值實例化對應(yīng)的類
  5. 調(diào)用對應(yīng)類上面的處理任務(wù)方法
  6. 修改任務(wù)狀態(tài)
  7. 打印日志

在這整個流程中,各個需要使用任務(wù)引擎的業(yè)務(wù)方其實只需要關(guān)注,寫入任務(wù)執(zhí)行任務(wù)這兩個事件绰咽。其余的任務(wù)引擎會自動完成

表設(shè)計菇肃、架構(gòu)設(shè)計

數(shù)據(jù)表結(jié)構(gòu)如下:

字段 類型 注釋
id int(11) 唯一主鍵
bus_type int(11) 業(yè)務(wù)類型
bus_key bigint(20) 業(yè)務(wù)id
remark varchar(255) 備注
params varchar(255) 可能會用到的參數(shù)
status tinyint(1) 任務(wù)狀態(tài) 1完成 2待執(zhí)行 3結(jié)束 4失敗重試
create_time int(11) 創(chuàng)建時間
update_time int(11) 更新時間

索引設(shè)計,結(jié)合業(yè)務(wù)

索引名 索引字段
idx_status_create_time INDEX cp_schedule_task(status ASC, create_time DESC)

開發(fā)框架我使用的是PHP7.3+ThinkPHP6.0
首先使用composer create-project topthink/think tp6拉取一個框架代碼取募。
框架生成后琐谤,需要配置一些數(shù)據(jù)庫連接之類的東西我就不介紹了,感興趣的朋友可以前tp6官方手冊里面查看
首先我們將需要寫的代碼分為三部分

  1. 添加任務(wù)
  2. 做任務(wù)
  3. 修改任務(wù)狀態(tài)

首先來看添加任務(wù)玩敏。
添加任務(wù)實質(zhì)上就是實現(xiàn)一個寫表的操作斗忌,封裝成接口對外調(diào)用就可以了

創(chuàng)建模型類

class ScheduleTask extends Model
{
    protected $autoWriteTimestamp=true;//配置參數(shù),配置之后旺聚,會自動寫create_time和update_time
}

創(chuàng)建業(yè)務(wù)類织阳,并且創(chuàng)建新增方法

/**
 * 處理任務(wù)業(yè)務(wù)類
 * Class ScheduleTaskService
 * @package app\schedule\service
 */
class ScheduleTaskService
{
 /**
     * @title 新增任務(wù)方法
     * @param $data ["bus_id"=>1234,"bus_type"=>1,"remark"=>"hhhh","params"=>"{'lll':'hhh'}"]
     * @special bus_id 和bus_type必填
     */
    public function addTask($data){
        ScheduleTask::create($data);
    }
}

創(chuàng)建控制器,并且創(chuàng)建對外API

class Task extends  BaseController
{

    /**
     * @title 新增任務(wù)接口
     */
    public function add()
    {
        $post=$this->request->param();
        //其實需要對入?yún)⒆雠袛嗯榇猓@里就先省略
        $service=new ScheduleTaskService();
        $service->addTask($post);
        response("加入成功",200,[],"json");
    }

}

第二步就是任務(wù)的重點做任務(wù)
由于各個業(yè)務(wù)方的需求共同點是做任務(wù)唧躲,而任務(wù)的內(nèi)容又不盡相同。所以我們需要將做這個任務(wù)給抽象出接口(interface)而任務(wù)內(nèi)容由各個業(yè)務(wù)方自己實現(xiàn)

先創(chuàng)建一個對象接口,定義執(zhí)行任務(wù)的方法碱璃。有了這個接口弄痹,后面業(yè)務(wù)方需要添加不同的業(yè)務(wù)都可以繼承這個接口并實現(xiàn)run方法

interface ScheduleTaskComponent
{
    /**
     * 執(zhí)行任務(wù)
     * @param $data array 從表里出的一條數(shù)據(jù)
     * @return mixed
     */
    public function run($data);
}

實現(xiàn)一個假裝發(fā)送消息的任務(wù)消耗類

class SendMessageSchedule implements ScheduleTaskComponent
{
    /**
     * 執(zhí)行任務(wù)
     * @param $data array 從表里出的一條數(shù)據(jù)
     * @return mixed
     */
    public function run($data)
    {
        echo "假裝發(fā)送了一條消息".$data['bus_id'].$data['params'].PHP_EOL;
    }
}

再實現(xiàn)一個假裝寫日志的任務(wù)消耗類


class AddLogSchedule implements ScheduleTaskComponent
{

    /**
     * 執(zhí)行任務(wù)
     * @param $data array 從表里出的一條數(shù)據(jù)
     * @return mixed
     */
    public function run($data)
    {
        echo "假裝寫了一條日志".$data['remark'].PHP_EOL;
    }
}

那么現(xiàn)在消耗任務(wù)的方法有了,怎么才能讓任務(wù)池里的任務(wù)根據(jù)不同的業(yè)務(wù)調(diào)用不同的方法呢嵌器?這也是本文的關(guān)鍵肛真。
既然是計劃任務(wù),傳統(tǒng)的方式一般是嘴秸,寫一個控制器里面全部都是計劃任務(wù)的業(yè)務(wù)毁欣。然后通過linux自帶的crontab來定時發(fā)起curl請求。這樣做方便是方便岳掐,但是還是有幾個缺點~
比如linux的計劃任務(wù)最小時間單位是分鐘凭疮,還有就是通過curl請求會走一次公網(wǎng)域名解析產(chǎn)生回環(huán)調(diào)用。就算你直接寫ip不走域名串述。也會對nginx造成壓力执解。
為了解決這個問題,我就想到使用php的CLI模式去調(diào)用纲酗,這樣就會直接執(zhí)行php腳本衰腌,不走nginx。比較節(jié)約資源觅赊,而且更加靈活右蕊。
起初我是打算寫控制器然后cli的形式調(diào)用就好,但是沒想到tp6不支持這樣調(diào)用了吮螺。隨后我研究了下tp的文檔饶囚,發(fā)現(xiàn)tp6應(yīng)該是更希望你使用他的自定義指令帕翻。看了文檔之后發(fā)現(xiàn)其實非常的簡單

  1. 先定義一個自定義命令
php think make:command app\\schedule\\command\\scheduleStart 任務(wù)引擎啟動

這里上面的命令有個小問題萝风,和文檔里有些出入嘀掸,文檔里說的是不需要轉(zhuǎn)移也就是只需要用一個\隔開命名空間的。但是試了下有問題规惰。我看了下源碼睬塌,應(yīng)該是他們的bug。已經(jīng)給作者反應(yīng)了歇万,應(yīng)該在下個版本會修復(fù)

隨后就會幫你生成好一個用于命令行的類


生成的類中會有兩個方法 一個configureexecute

configure可以配置接收參數(shù)揩晴,不過我們這次沒有用。直接來看execute方法贪磺。顧名思義文狱,這個是用來執(zhí)行的方法
我們來看代碼

protected function execute(Input $input, Output $output)
    {
        // 指令輸出
        $output->writeln('任務(wù)引擎啟動');
        while (true){//死循環(huán)
            $service = new ScheduleTaskService();
            //在表里獲取狀態(tài)為未執(zhí)行、失敗重試的100條任務(wù)
            $task100 = $service->get100Task();
            foreach ($task100 as $item) {
                $updateId=$item['id'];
                $updateRemark='';
                try {
                    $type = $item['bus_type'];
                    //工廠模式缘挽,通過type生產(chǎn)不同的類實例
                    $taskComponent = ScheduleTaskService::getTaskComponent($type);
                    //調(diào)用類上的run方法
                    $taskComponent->run($item);
                    $updateStatus=1;
                } catch (BusinessException $be) {//約定的業(yè)務(wù)異常
                    $updateRemark=$be->getMessage();
                    //如果異常是101就視為失敗,不會重復(fù)執(zhí)行
                    if ($be->getCode()=="101") {
                        //報錯呻粹,不執(zhí)行
                        $updateStatus=3;
                    }else{
                        //如果不是101就說明壕曼,還有的救,改為狀態(tài)為再執(zhí)行
                        $updateStatus=4;
                    }
                } catch (Exception $e) {//系統(tǒng)異常
                    //系統(tǒng)異常
                    Log::error($e->getMessage());
                    $updateStatus=3;//將執(zhí)行狀態(tài)改為失敗
                    $updateRemark=$e->getMessage();
                }
                //修改任務(wù)狀態(tài)
                $service->updateTask($updateId,$updateStatus,$updateRemark);
            }
            //打印本次操作了多少條
            Log::info("Command/scheduleStart 任務(wù)完成數(shù){num}",['num'=>$task100->count()]);
            //線程掛起60秒
            sleep(60);
        }
    }

上面的代碼中等浊,有一個生產(chǎn)對應(yīng)任務(wù)的對象的方法getTaskComponent腮郊。值得拆開講一下
入?yún)⑹且粋€任務(wù)的bus_type。每個type代表不同的業(yè)務(wù)筹燕,也代表了不同的類轧飞。而type和類的對應(yīng)關(guān)系呢,我選擇了tp內(nèi)部的配置文件來做撒踪。其實也可以用戶數(shù)據(jù)庫記錄對應(yīng)關(guān)系过咬,也可以直接冗余到每條數(shù)據(jù)上≈仆看大家的需求

 /**
     * 獲取一個任務(wù)類的實現(xiàn)對象
     * @param $type int 任務(wù)的typeId
     * @return ScheduleTaskComponent
     * @throws BusinessException
     */
    public static function getTaskComponent($type){
        //根據(jù)配置type獲取配置
        $className=config("register.$type");
        if (empty($className)) {
            //如果沒有配置掸绞,則拋出不再執(zhí)行的業(yè)務(wù)異常
            Log::error("配置參數(shù){type}在注冊文件中不存在",['type',$type]);
            throw new BusinessException("101","配置參數(shù)錯誤");
        }
        //實例化對象
        return new $className();
    }

配置的話 我就在config文件夾下新建了一個配置


配置目錄

配置內(nèi)容如下

return [
    1=>"app\schedule\impl\SendMessageSchedule",//假裝發(fā)消息
    2=>"app\schedule\impl\AddLogSchedule"http://假裝寫日志
];

這樣就可以通過配置取到不同業(yè)務(wù)的處理任務(wù)類。最后調(diào)用從接口繼承的run方法完成調(diào)用

啟動

準(zhǔn)備4條數(shù)據(jù)
#在項目根目錄使用命令啟動自定義命令
 php think scheduleStart
執(zhí)行結(jié)果

由于我的任務(wù)執(zhí)行類就是打印一個字符串耕捞,所以能在控制臺里看到


數(shù)據(jù)表里的狀態(tài)

數(shù)據(jù)庫里的任務(wù)狀態(tài)也是修改為完成狀態(tài)了衔掸。這個時候,任務(wù)引擎會持續(xù)的監(jiān)聽數(shù)據(jù)表里的任務(wù)俺抽。發(fā)現(xiàn)有任務(wù)就會去執(zhí)行它敞映。任勞任怨永不停止

本文 源碼 在github上面,需要自取

結(jié)語

首先很感謝你能看到這里磷斧,本次介紹的工具只為學(xué)習(xí)使用振愿,有很多細(xì)節(jié)還有待考究捷犹,如果需要在生產(chǎn)環(huán)境使用,一定要控制好入?yún)⑿r灪湾e誤預(yù)警埃疫。如果你覺得我的博客對你有幫助的話伏恐,點個贊再走唄。最后在新年之際栓霜,給各位讀者拜個年翠桦! 大家新年快樂~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市胳蛮,隨后出現(xiàn)的幾起案子销凑,更是在濱河造成了極大的恐慌,老刑警劉巖仅炊,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件斗幼,死亡現(xiàn)場離奇詭異,居然都是意外死亡抚垄,警方通過查閱死者的電腦和手機(jī)蜕窿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來呆馁,“玉大人桐经,你說我怎么就攤上這事≌懵耍” “怎么了阴挣?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長纺腊。 經(jīng)常有香客問我畔咧,道長,這世上最難降的妖魔是什么揖膜? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任誓沸,我火速辦了婚禮,結(jié)果婚禮上次氨,老公的妹妹穿的比我還像新娘蔽介。我一直安慰自己,他們只是感情好煮寡,可當(dāng)我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布虹蓄。 她就那樣靜靜地躺著,像睡著了一般幸撕。 火紅的嫁衣襯著肌膚如雪薇组。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天坐儿,我揣著相機(jī)與錄音律胀,去河邊找鬼宋光。 笑死,一個胖子當(dāng)著我的面吹牛炭菌,可吹牛的內(nèi)容都是我干的罪佳。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼黑低,長吁一口氣:“原來是場噩夢啊……” “哼赘艳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起克握,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蕾管,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后菩暗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掰曾,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年停团,在試婚紗的時候發(fā)現(xiàn)自己被綠了旷坦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡佑稠,死狀恐怖塞蹭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情讶坯,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布岗屏,位于F島的核電站辆琅,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏这刷。R本人自食惡果不足惜婉烟,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望暇屋。 院中可真熱鬧似袁,春花似錦、人聲如沸咐刨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽定鸟。三九已至而涉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間联予,已是汗流浹背啼县。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工材原, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人季眷。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓余蟹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親子刮。 傳聞我的和親對象是個殘疾皇子威酒,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 一、簡歷準(zhǔn)備 1话告、個人技能 (1)自定義控件兼搏、UI設(shè)計、常用動畫特效 自定義控件 ①為什么要自定義控件沙郭? Andr...
    lucas777閱讀 5,213評論 2 54
  • 高中的時候佛呻,自己的同桌是個話癆,而且不知道為什么哪里的事情他都能聽得到病线,所以自己莫名其妙一個比較宅吓著,沒有故事的人,...
    不像話的故事閱讀 102評論 0 0
  • 父母和性別父母對于孩子如何形成性別認(rèn)同有著強(qiáng)烈的影響送挑,你的寶寶并不是生來就知道自己是男孩還是女孩的绑莺,或者這兩個詞是...
    潼寶的開心果閱讀 106評論 0 0
  • Binner閱讀 465評論 0 49
  • 第十三章 勇敢的向前一步 桑德伯格邀請了她女性好友們來探討女性平等問題,這里庫納爾.莫迪是麥肯錫咨詢公司顧...
    冰清_e7cc閱讀 229評論 0 0