一篇文章:
laravel中的隊列服務跟其他隊列服務也沒有什么不同,都是最符合人類思維的最簡單最普遍的流程:有一個地方存放隊列信息,一個PHP進程在運行時將任務寫入,另外一個PHP守護進程輪詢隊列信息,將達到執行要求的任務執行並刪除。由於PHP是url驅動的同步語言,本身是阻塞的,所以laravel提供一個守護進程工具來查詢並執行隊列信息也就不足為奇了。
Laravel的queue配置文件是 /app/config/queue.php,在 Default Queue Driver 這一項中,可以選擇"sync", "beanstalkd", "sqs", "iron", "redis" 五種驅動器。
配置文件:
queue.php
'redis' => array(
'driver' => 'redis',
'queue' => 'default',
),
database.php:
/*
|--------------------------------------------------------------------------
| Redis Databases
|--------------------------------------------------------------------------
|
| Redis is an open source, fast, and advanced key-value store that also
| provides a richer set of commands than a typical key-value systems
| such as APC or Memcached. Laravel makes it easy to dig right in.
|
*/
'redis' => array(
'cluster' => false,
'default' => array(
'host' => '127.0.0.1',
'port' => 6379,
'database' => 'laravelFirst',
),
),
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文件的路徑請自行調整。
大家可能注意到了,我們要使用的這個隊列系統用到了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類會在每一個 / 前面加上兩個 \\ 。這對於一些特殊操作可能會造成致命影響。(開玩笑,有上面那個致命么!)
------------------
我的使用,在app/service存放一個文件:
class SendEmail{ public function send($job,$data) { system(SEND_MAIL); $job->delete(); } }
某個地方放入隊列:
Queue::push('SendEmail@send', array('message' =>"hello world"));
有時候queue default會有2個queue:
-
default (2)
一篇文章:
---
利用Redis可以很方便的實現一個任務隊列,但是在Laravel中,Redis的隊列總會出現一個任務多次執行的問題。究其原因是它寫死了reserved的時長,也就是如果1分鍾后任務沒有執行完成,那么這個任務就會被重新放回隊列。下面是隊列的簡單使用和執行原理。
設置
設置隊列使用Redis非常容易,在app/config/queue.php
中配置
... 'default' => 'redis', ... 'connections' => array( ... 'redis' => array( 'driver' => 'redis', 'queue' => 'waa', ), ),
即可。
使用
使用時不需要多配置,只要寫好Queue類和其fire方法,在需要的位置出隊即可。具體方法可以看這里。
class SendEmail { public function fire($job, $data) { // $job->delete(); } } Queue::push('SendEmail@send', array('message' => $message));
流程
Laravel利用artisan命令來執行出隊操作,然后進行任務的執行。方法調用如下:
- artisan queue:work
- WorkerCommand:fire()
- Worker:pop()
- Worker:getNextJob()
- RedisQueue:pop()
- Worker:process()
我遇到的問題就在這里,在RedisQueue:pop()
方法中,有這樣一句:
$this->redis->zadd($queue.':reserved', $this->getTime() + 60, $job);
這里將當前執行的任務放到另外一個reserved隊列中,超時時間是60s。也就是說,如果60s后這個任務沒有被刪除掉,則任務會重新被放入隊列中來。因此,在實際的使用過程中,任務很可能被多次執行。解決的辦法是
class SendEmail { public function fire($job, $data) { $job->delete(); // job } }
即先刪除這個任務,再開始執行任務
參考:http://yansu.org/2014/04/11/redis-queue-in-laravel.html
supervisor管理queue:http://yansu.org/2014/03/22/managing-your-larrvel-queue-by-supervisor.html