php如何使用Swoole實(shí)現(xiàn)毫秒級(jí)定時(shí)任務(wù)

項(xiàng)目開發(fā)中,如果有定時(shí)任務(wù)的業(yè)務(wù)要求际看,我們會(huì)使用linux的crontab來解決,但是它的最小粒度是分鐘級(jí)別矢否,如果要求粒度是秒級(jí)別的仲闽,甚至毫秒級(jí)別的,crontab就無法滿足僵朗,值得慶幸的是swoole提供的強(qiáng)大的毫秒定時(shí)器赖欣。

應(yīng)用場(chǎng)景舉例

我們可能會(huì)遇到這樣的場(chǎng)景:

  • 場(chǎng)景一:每隔30秒獲取一次本機(jī)內(nèi)存使用率

  • 場(chǎng)景二:2分鐘后執(zhí)行報(bào)表發(fā)送任務(wù)

  • 場(chǎng)景三:每天凌晨2點(diǎn)鐘定時(shí)請(qǐng)求第三方接口,如果接口有數(shù)據(jù)返回則停止任務(wù)验庙,如果接口由于某種原因沒有響應(yīng)或者沒有數(shù)據(jù)返回則5分鐘后繼續(xù)嘗試請(qǐng)求該接口畏鼓,嘗試5次后仍然失敗則停止該任務(wù)

以上的三個(gè)場(chǎng)景我們都可以歸納為定時(shí)任務(wù)的范疇。

Swoole毫秒定時(shí)器

Swoole提供了異步毫秒定時(shí)器函數(shù):

swoole_timer_tick(int $msec, callable $callback):設(shè)置一個(gè)間隔時(shí)鐘定時(shí)器壶谒,每隔msec毫秒執(zhí)行一次callback,類似于javascript中的setInterval()膳沽。

swoole_timer_after(int $after_time_ms, mixed $callback_function):在指定的時(shí)間$after_time_ms后執(zhí)行$callback_function汗菜,類似于javascript的setTimeout()

swoole_timer_clear(int $timer_id):刪除指定id的定時(shí)器挑社,類似于javascript的clearInterval()陨界。

解決方案

對(duì)于場(chǎng)景一,經(jīng)常用在系統(tǒng)檢測(cè)統(tǒng)計(jì)方面痛阻,實(shí)時(shí)性要求比較高菌瘪,但又能控制好頻率,多用于后臺(tái)服務(wù)器性能監(jiān)控阱当,可以生成可視化圖表俏扩。可以是30秒獲取一次內(nèi)存使用率弊添,也可以是10秒录淡,而crontab最小粒度只能設(shè)置為1分鐘。

swoole_timer_tick(30000, function($timer) use ($task_id) { // 啟用定時(shí)器油坝,每30秒執(zhí)行一次  
    $memPercent = $this->getMemoryUsage(); //計(jì)算內(nèi)存使用率  
    echo date('Y-m-d H:i:s') . '當(dāng)前內(nèi)存使用率:'.$memPercent."\n";  
});  

對(duì)于場(chǎng)景二嫉戚,直接定義xx時(shí)間后執(zhí)行某項(xiàng)任務(wù)的話刨裆,貌似crontab比較困難,而使用swoole的swoole_timer_after可以實(shí)現(xiàn):

swoole_timer_after(120000, function() use ($str) { //2分鐘后執(zhí)行  
    $this->sendReport(); //發(fā)送報(bào)表  
    echo "send report, $str\n";  
});  

對(duì)于場(chǎng)景三彬檀,用來作嘗試請(qǐng)求帆啃,請(qǐng)求失敗后繼續(xù),如果成功則停止請(qǐng)求窍帝。用crontab也能解決努潘,但是比較傻,比如設(shè)置每隔5分鐘請(qǐng)求一次盯桦,不管成功會(huì)失敗都會(huì)去執(zhí)行一次慈俯。而用swoole定時(shí)器則智能多了。

swoole_timer_tick(5*60*1000, function($timer) use ($url) { // 啟用定時(shí)器拥峦,每5分鐘執(zhí)行一次  
      $rs = $this->postUrl($url);  

      if ($rs) {  
          //業(yè)務(wù)代碼...  
          swoole_timer_clear($timer); // 停止定時(shí)器  
          echo date('Y-m-d H:i:s'). "請(qǐng)求接口任務(wù)執(zhí)行成功\n";  
      } else {  
          echo date('Y-m-d H:i:s'). "請(qǐng)求接口失敗贴膘,5分鐘后再次嘗試\n";  
     }  
 });  

示例代碼

新建文件\src\App\Task.php:

namespace Helloweba\Swoole;  

use swoole_server;  

/**  
* 任務(wù)調(diào)度  
*/  
class Task  
{  
    protected $serv;  
    protected $host = '127.0.0.1';  
    protected $port = 9506;  
    // 進(jìn)程名稱  
    protected $taskName = 'swooleTask';  
    // PID路徑  
    protected $pidPath = '/run/swooletask.pid';  
    // 設(shè)置運(yùn)行時(shí)參數(shù)  
    protected $options = [  
        'worker_num' => 4, //worker進(jìn)程數(shù),一般設(shè)置為CPU數(shù)的1-4倍   
        'daemonize' => true, //啟用守護(hù)進(jìn)程  
        'log_file' => '/data/log/swoole-task.log', //指定swoole錯(cuò)誤日志文件  
        'log_level' => 0, //日志級(jí)別 范圍是0-5,0-DEBUG略号,1-TRACE刑峡,2-INFO,3-NOTICE玄柠,4-WARNING突梦,5-ERROR  
        'dispatch_mode' => 1, //數(shù)據(jù)包分發(fā)策略,1-輪詢模式  
        'task_worker_num' => 4, //task進(jìn)程的數(shù)量  
        'task_ipc_mode' => 3, //使用消息隊(duì)列通信,并設(shè)置為爭(zhēng)搶模式  
    ];  

    public function __construct($options = [])  
    {  
        date_default_timezone_set('PRC');  
        // 構(gòu)建Server對(duì)象羽利,監(jiān)聽127.0.0.1:9506端口  
        $this->serv = new swoole_server($this->host, $this->port);  

        if (!empty($options)) {  
            $this->options = array_merge($this->options, $options);  
        }  
        $this->serv->set($this->options);  

        // 注冊(cè)事件  
        $this->serv->on('Start', [$this, 'onStart']);  
        $this->serv->on('Connect', [$this, 'onConnect']);  
        $this->serv->on('Receive', [$this, 'onReceive']);  
        $this->serv->on('Task', [$this, 'onTask']);   
        $this->serv->on('Finish', [$this, 'onFinish']);  
        $this->serv->on('Close', [$this, 'onClose']);  
    }  

    public function start()  
    {  
        // Run worker  
        $this->serv->start();  
    }  

    public function onStart($serv)  

    {  
        // 設(shè)置進(jìn)程名  
        cli_set_process_title($this->taskName);  
        //記錄進(jìn)程id,腳本實(shí)現(xiàn)自動(dòng)重啟  
        $pid = "{$serv->master_pid}\\n{$serv->manager_pid}";  
        file_put_contents($this->pidPath, $pid);  
    }  

    //監(jiān)聽連接進(jìn)入事件  
    public function onConnect($serv, $fd, $from_id)  
    {  
        $serv->send( $fd, "Hello {$fd}!" );  
    }  

    // 監(jiān)聽數(shù)據(jù)接收事件  
    public function onReceive(swoole_server $serv, $fd, $from_id, $data)  
    {  
        echo "Get Message From Client {$fd}:{$data}\n";  
        //$this->writeLog('接收客戶端參數(shù):'.$fd .'-'.$data);  
        $res['result'] = 'success';  
        $serv->send($fd, json_encode($res)); // 同步返回消息給客戶端  
        $serv->task($data);  // 執(zhí)行異步任務(wù)  
    }  

    /**  
  
    * @param $serv swoole_server swoole_server對(duì)象  
    * @param $task_id int 任務(wù)id  
    * @param $from\id int 投遞任務(wù)的worker_id  
    * @param $data string 投遞的數(shù)據(jù)  
    */  
    public function onTask(swoole_server $serv, $task_id, $from_id, $data)  
    {  
        swoole_timer_tick(30000, function($timer) use ($task_id) { // 啟用定時(shí)器宫患,每30秒執(zhí)行一次  
            $memPercent = $this->getMemoryUsage();  
            echo date('Y-m-d H:i:s') . '當(dāng)前內(nèi)存使用率:'.$memPercent."\n";  
        });  
    }  


    /**  
    * @param $serv swoole_server swoole_server對(duì)象  
    * @param $task_id int 任務(wù)id  
    * @param $data string 任務(wù)返回的數(shù)據(jù)  
    */  
    public function onFinish(swoole_server $serv, $task_id, $data)  
    {  
        //  
    }  

  
    // 監(jiān)聽連接關(guān)閉事件  
    public function onClose($serv, $fd, $from_id) {  
        echo "Client {$fd} close connection\n";  
    }  

    public function stop()  
    {  
        $this->serv->stop();  
    }  

    private function getMemoryUsage()  
    {  
        // MEMORY  
        if (false === ($str = @file("/proc/meminfo"))) return false;  
        $str = implode("", $str);  
        preg_match_all("/MemTotal\s{0,}\:+\s{0,}([\d\.]+).+?MemFree\s{0,}\:+\s{0,}([\d\.]+).+?Cached\s{0,}\:+\s{0,}([\d\.]+).+?SwapTotal\s{0,}\:+\s{0,}([\d\.]+).+?SwapFree\s{0,}\:+\s{0,}([\d\.]+)/s", $str, $buf);  
        //preg_match_all("/Buffers\s{0,}\:+\s{0,}([\d\.]+)/s", $str, $buffers);  

        $memTotal = round($buf[1][0]/1024, 2);  
        $memFree = round($buf[2][0]/1024, 2);  
        $memUsed = $memTotal - $memFree;  
        $memPercent = (floatval($memTotal)!=0) ? round($memUsed/$memTotal*100,2):0;  

        return $memPercent;  
    }  
}  

我們以場(chǎng)景一為例,在onTask啟用定時(shí)任務(wù)这弧,每隔30秒計(jì)算一次內(nèi)存使用率娃闲。實(shí)際應(yīng)用中可以把計(jì)算好的內(nèi)存按時(shí)間寫入數(shù)據(jù)庫等存儲(chǔ)中,然后可以根據(jù)前端需求用來渲染成統(tǒng)計(jì)圖表匾浪,如:

1.png

接著服務(wù)端代碼 public\taskServer.php :

<?php
require dirname(__DIR__) . '/vendor/autoload.php';  
use Helloweba\Swoole\Task;  
$opt = [  
    'daemonize' => false  
];  
$ser = new Task($opt);  
$ser->start();  

客戶端代碼 public\taskClient.php :

<?php
class Client  
{  
    private $client;  
    public function __construct() {  
        $this->client = new swoole_client(SWOOLE_SOCK_TCP);  
    }  
    public function connect() {  
        if( !$this->client->connect("127.0.0.1", 9506 , 1) ) {  
            echo "Error: {$this->client->errMsg}[{$this->client->errCode}]\n";  
          }  
        fwrite(STDOUT, "請(qǐng)輸入消息 Please input msg:");  
        $msg = trim(fgets(STDIN));  
        $this->client->send( $msg );  
        $message = $this->client->recv();  
        echo "Get Message From Server:{$message}\n";  
    }  
}  
$client = new Client();  
$client->connect();  

驗(yàn)證效果

1.啟動(dòng)服務(wù)端:

php taskServer.php

2.客戶端輸入:

另開命令行窗口皇帮,執(zhí)行

[root@localhost public]# php taskClient.php  

請(qǐng)輸入消息 Please input msg:hello

Get Message From Server:{"result":"success"}  
[root@localhost public]#  

3.服務(wù)端返回:

2.png

如果返回上圖中的結(jié)果,則定時(shí)任務(wù)正常運(yùn)行蛋辈,我們會(huì)發(fā)現(xiàn)每隔30秒會(huì)輸出一條信息属拾。


更多學(xué)習(xí)內(nèi)容可以訪問【對(duì)標(biāo)大廠】精品PHP架構(gòu)師教程目錄大全,只要你能看完保證薪資上升一個(gè)臺(tái)階(持續(xù)更新)

以上內(nèi)容希望幫助到大家冷溶,很多PHPer在進(jìn)階的時(shí)候總會(huì)遇到一些問題和瓶頸渐白,業(yè)務(wù)代碼寫多了沒有方向感,不知道該從那里入手去提升逞频,對(duì)此我整理了一些資料礼预,包括但不限于:分布式架構(gòu)、高可擴(kuò)展虏劲、高性能托酸、高并發(fā)褒颈、服務(wù)器性能調(diào)優(yōu)、TP6励堡,laravel谷丸,YII2,Redis应结,Swoole刨疼、Swoft、Kafka鹅龄、Mysql優(yōu)化揩慕、shell腳本、Docker扮休、微服務(wù)迎卤、Nginx等多個(gè)知識(shí)點(diǎn)高級(jí)進(jìn)階干貨需要的可以免費(fèi)分享給大家,需要的可以加入我的PHP技術(shù)交流群953224940

進(jìn)階PHP月薪30k>>>架構(gòu)師成長(zhǎng)路線【視頻玷坠、面試文檔免費(fèi)獲取】

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蜗搔,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子八堡,更是在濱河造成了極大的恐慌樟凄,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,496評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兄渺,死亡現(xiàn)場(chǎng)離奇詭異缝龄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)挂谍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,187評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門二拐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人凳兵,你說我怎么就攤上這事∑笕恚” “怎么了庐扫?”我有些...
    開封第一講書人閱讀 157,091評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)仗哨。 經(jīng)常有香客問我形庭,道長(zhǎng),這世上最難降的妖魔是什么厌漂? 我笑而不...
    開封第一講書人閱讀 56,458評(píng)論 1 283
  • 正文 為了忘掉前任萨醒,我火速辦了婚禮,結(jié)果婚禮上苇倡,老公的妹妹穿的比我還像新娘富纸。我一直安慰自己囤踩,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,542評(píng)論 6 385
  • 文/花漫 我一把揭開白布晓褪。 她就那樣靜靜地躺著堵漱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涣仿。 梳的紋絲不亂的頭發(fā)上勤庐,一...
    開封第一講書人閱讀 49,802評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音好港,去河邊找鬼愉镰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛钧汹,可吹牛的內(nèi)容都是我干的丈探。 我是一名探鬼主播,決...
    沈念sama閱讀 38,945評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼崭孤,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼类嗤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起辨宠,我...
    開封第一講書人閱讀 37,709評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤遗锣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后嗤形,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體精偿,經(jīng)...
    沈念sama閱讀 44,158評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,502評(píng)論 2 327
  • 正文 我和宋清朗相戀三年赋兵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了笔咽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,637評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡霹期,死狀恐怖叶组,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情历造,我是刑警寧澤甩十,帶...
    沈念sama閱讀 34,300評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站吭产,受9級(jí)特大地震影響侣监,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜臣淤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,911評(píng)論 3 313
  • 文/蒙蒙 一橄霉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧邑蒋,春花似錦姓蜂、人聲如沸按厘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,744評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽刻剥。三九已至甘苍,卻和暖如春弟塞,著一層夾襖步出監(jiān)牢的瞬間旁钧,已是汗流浹背船惨。 一陣腳步聲響...
    開封第一講書人閱讀 31,982評(píng)論 1 266
  • 我被黑心中介騙來泰國打工反粥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留应闯,地道東北人茸习。 一個(gè)月前我還...
    沈念sama閱讀 46,344評(píng)論 2 360
  • 正文 我出身青樓柔逼,卻偏偏與公主長(zhǎng)得像挟裂,于是被迫代替她去往敵國和親享钞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,500評(píng)論 2 348

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