MemQ 實現非同步任務

菩提樹下的煮茶小童子發表於2017-12-19

這幾天在做推送相關的任務的時候發現了一段神奇的程式碼。

$pushmsg = new NormalPushMsg($userid, $content, $clickurl,"");
PushService::getInstance()->sendPushToMemq($pushmsg);
複製程式碼

一開始的時候我還納悶,為什麼不直接發呢,走這麼大一圈子彎路到底是為了啥,後來想了想,傳送push動輒幾十上百萬的使用者,會是一個很耗時的操作。短時間會對伺服器造成不小的壓力。而通過memq這樣一種曲線救國的套路,倒也還是一個不錯的方法。

下面就自己動手,也來實現一下這樣的功能。

準備

總用量 16
-rw-r--r-- 1 root root  82 12月 19 14:15 config.ini
-rw-r--r-- 1 root root 339 12月 19 14:36 publish_tasks.php
-rw-r--r-- 1 root root 387 12月 19 14:38 runjobs_background.php
-rw-r--r-- 1 root root 176 12月 19 14:13 worker.php
複製程式碼

config.ini

內容格式大概可以是下面的形式。

queuename = default
queueip = 192.168.1.1
queueport = 22201
classname = Worker
複製程式碼

下面來講講每個引數對應的含義。

  • queuename 是我們要進行存放的佇列的名稱
  • queueip 即會在那臺主機上執行對應的任務
  • queueport 對應主機上memcache的埠
  • classname 通過PHP的哪個類來執行對應的“非同步”方法。

確切的來講,對公司而言queueip和queueport是必須的,因為你會管理很多的主機,如果不進行限制,非同步任務變多的時候,那就是一個災難。但是對於本次試驗而言,就沒什麼必要了。

worker.php

這個檔案就是我們會具體呼叫的類檔案,程式碼可以根據具體的業務需求而定。我這裡只是演示一下,就隨便寫了。

<?php
class Worker {

    public function __construct() {
    }
    public function saveToLocal($msg="default data") {
        LiveLog("./worker.log", $msg);
    }
}
複製程式碼

這個類的名稱和上面的config.ini檔案的classname保持一致就好了。否則會出現load錯誤的問題。

publish_tasks.php

按照一開始描述的,這個檔案起到一個釋出任務的作用。好比開啟了一個給XXX使用者群傳送push召回的任務。這裡還是簡單的寫一下。

<?php
header("Content-Type:text/html;charset=UTF-8");
require "/home/wwwroot/api.newtv.com/common/common.inc.php";

$memq = new useLiveMemQueue();
$key = "my:memq:test";
$msg = "愛過,一個人的春夏秋冬!";
$result = $memq->cache($key, "saveToLocal('$msg')");
var_dump($result);
複製程式碼

這裡面有一些公司封裝好的API,比如useLiveMemQueue類以及內部對應的cache方法。涉及到公司隱私性,就不再貼上了。但是這都是對memcache的簡單的封裝,你自己花一點點時間也能寫得出來的。

runjobs_background.php

任務已經被髮布到了memq相應的佇列中了,接下來就是要去消費它,否則很有可能導致伺服器記憶體吃緊,那你的手機報警就哐哐的響了吧。

<?php
header("Content-Type:text/html;charset=UTF-8");
require "/home/wwwroot/api.newtv.com/common/common.inc.php";
require __DIR__."/worker.php";

$executor = null;
$memq = new useLiveMemQueue();
$method = trim($memq->get("my:memq:test"));
$classname = "Worker";
$executor = new $classname();

$execstr = "\$executor->$method".";";

$result = eval($execstr);
var_dump($result);

複製程式碼

具體的原理,看完程式碼應該就明白了。其中最關鍵的就是PHP這門動態語言的優勢。否則要使用Java這種編譯性語言的話,還需要一套相應的反射機制了。

定時任務

一般來說,我們會寫一個crontab指令碼來定期的去“消費”memq中的“非同步”任務,比如針對上面的實驗,我們就可以寫這樣的一個crontab。

*/10 * * * * cd /home/wwwroot/workspace/mars/background/ && php runjobs_background.php 2>&1
複製程式碼

每10分鐘執行一次,相應的佇列就會被消費掉了。但是這個10分鐘只是一個頻率。因為我們要進行消費的任務量會很大,10分鐘內根本跑不完,在第二個10分鐘的時候cron會再次開啟一個程式,來進行消費。以此類推,就有可能會出現多個程式來消費相同的任務。

這個只要在不影響伺服器效能的情況下,是沒什麼影響的,瞭解這麼個情況就可以了。

驗證結果

簡單驗證

好了,crontab部署後的10分鐘已經到了,下面就來看看有沒有生成

底層API
對應的worker.log檔案吧。
成功生成了日誌檔案
“非同步”執行結果

大規模測試

剛才只是在memq裡面存放了一條“非同步”任務,下面我們可以來試試多放點任務來試試。比如我們迴圈1000次cache操作,相當於在佇列中存放了1000個待消費的任務。經過消費後,得到的結果如下。

檢視worker.log結果

總結

到此,本次試驗就結束了。相比較於自己隨意的寫程式碼,公司的程式碼庫更具有實際意義,很多工程上的思維是學校的老師教不了的。多實踐下,才能明白自身的不足之處。

這裡使用的是PHP,所以在runjobs_background.php中執行起來才會很方便。如果使用其他的動態(解釋性)語言,也會有如此感受。當然使用Java的話,就需要再寫一個反射的工具類,多了一步,但也還是很方便的。

相關文章