這半個月,我參與重寫了一個微信公眾號後端系統,首次使用了laravel 4.2,以及laravel引以為傲的隊列服務(queue)。
由於整個系統涉及到多端交互,又有大量語音傳輸、處理的業務,我們在一些地方發現響應時間過長。之前的系統基於node.js和mongoDB,由於node天生就是異步,有守護進程,所以並沒有出現過這個問題,而這次重寫必然要引入異步流程了。Queue進入了我們的視線。
根據這一頁幾乎還全是英文的”中文文檔“ ,laravel恰好在4.2版本中剛剛引入了redis作為隊列存儲,這是一個非常好的消息。OK,背景介紹到這裡,下面扯扯干貨。
laravel中的隊列服務跟其他隊列服務也沒有什麼不同,都是最符合人類思維的最簡單最普遍的流程:有一個地方存放隊列信息,一個PHP進程在運行時將任務寫入,另外一個PHP守護進程輪詢隊列信息,將達到執行要求的任務執行並刪除。由於PHP是url驅動的同步語言,本身是阻塞的,所以laravel提供一個守護進程工具來查詢並執行隊列信息也就不足為奇了。
Laravel的queue配置文件是 /app/config/queue.php,在 Default Queue Driver 這一項中,可以選擇"sync", "beanstalkd", "sqs", "iron", "redis" 五種驅動器。
1. sync是本地調試用的同步驅動器
2. beanstalkd 是一個專業隊列服務驅動器:http://kr.github.io/beanstalkd/
3. sqs和iron是國外第三方隊列服務
4. 最後一項redis給了我們一個使用redis的理由,這樣我們順便把緩存服務和session服務全部遷移到redis上了。
0. 順便說一句,session驅動器千萬別用mysql,處理時間1S不是夢,哎,看誰呢,說的就是你,1S哥!
隊列服務需要專門新建任務類,作為獨立類,他們不需要繼承類,因為隊列裡的任務在執行的時候,是由PHP守護進程來獨立調用的,當然如果你要use一下別的類再調用,也不會出錯。之前我把很多額外服務獨立到了一個單獨的文件夾 /app/services 裡,比如輸入信息驗證 validator,特殊安全驗證模塊等,這次queue類們就位於其中。
queue的使用非常簡單,下面就是一個簡單的示例:
復制代碼 代碼如下:
use Queue;
Queue::push('CurlJsonQueue', [
'url' => $url,
'json' => $json
]);
這就是一個標准的queue壓入流程了。當然,在這裡我把CurlJsonQueue類放到了services根目錄下,這個目錄已經被我注冊到composer.json的"autoload"的"classmap"中,是位於頂層命名空間中的,可以直接調用,如果需要調用非頂層命名空間,是可以寫 App\OOXX 的。我們的系統需要大量和微信服務器交互,所以就獨立出來了這個類。
復制代碼 代碼如下:
<?php
class CurlJsonQueue extends BaseController{
public function fire($job, $data)
{
$url = $data['url'];
$json = $data['json'];
parent::base_post_curl($url, $json);
$job->delete();
}
}
這個類默認的方法是 fire() ,參數也是固定的兩個 $job 和 $data,由於我在BaseController中封裝了post的curl模塊,所以就調用了一下。另外這裡還有一個小坑,當時寫base_post_curl() 的時候用的protected,導致use BaseController無效,必須繼承。
通過執行上面的代碼,queue中就被放入了一個新的任務,laravel通過下面的命令開啟守護進程:
復制代碼 代碼如下:
php artisan queue:listen
然後守護進程就開始處理隊列了。此代碼中的PHP命令和artisan文件的路徑請自行調整。
大家可能注意到了,我們要使用的這個隊列系統用到了redis和PHP命令行,如果在測試環境,加個開機啟動甚至是手動啟動都可以,但是在生產環境就需要更穩固的工具來守護這兩個程序,我們用的是supervisor,關於supervisor的安裝配置大家可以參考這篇文章: http://blog.segmentfault.com/qianfeng/1190000000532561 注意,文章裡有小坑請自行去踩。。。
OK,全部配置好之後,跑起來redis和PHP命令行,整個系統就開始愉快地運行啦~
使用感受:
隊列服務超好用,之前一次和app的交互流程需要6-7S,異步以後降低到2S以內,基本就是傳輸時間和PHP代碼運行時間了,耗時的特殊操作已經異步了。不過隊列服務默認1S開一個進程檢查一次redis中有沒有可以運行的服務,在阿裡雲服務器上,大約能占到單核的10%,消耗略大,而且隊列處理時間相對較長,因為沒有了之前同步時候的文件加載福利。不過如果有多個任務,PHP進程是會連續執行的,不會1S執行一個的啦。
下面說說坑:
1. 由於queue核心類使用了一個特殊函數,導致沒有明確類型的變量會以單元素數組的形式存進json,再存進redis。解決辦法就是在每一個要放進去的數據前面加上 ''. 。上面的$url和$json由於都已經在前面用引號進行了類型申明,故沒做這一步操作。
2. 如果要傳遞url給隊列,系統queue類會在每一個 / 前面加上兩個 \\ 。這對於一些特殊操作可能會造成致命影響。(開玩笑,有上面那個致命麼!)
使用Contains(你要檢查的內容)進行判斷(返回值布爾類型)。比如:namespace TestBed
{ public class MainTest
{ public static void Main()
{
int[] numbers = { 1,1,2,3,4,2,5,6,7};
Queue<int> q = new Queue<int>();
foreach (var item in numbers)
{
if (!q.Contains(item))
{
q.Enqueue(item);
}
}
} }
}
親,不一樣哦,第一個表達式是將隊列的指針控制在queuesize的范圍內循環跳轉,而第二個表達式指針將不斷向後跳轉一個節點的內存距離,很可能超出你定義的queuesize的有效內存范圍,占用未分配的內存編譯器會報錯滴哈,了解了不???