laravel隊列-讓守護進程處理耗時任務

待解決的問題

最近在作一個服務器集羣管理的web項目,須要處理一些極其耗時的操做,好比磁盤格式化分區。對於這個需求,最開始的想法是,爲了讓節點上的rpc(遠程過程調用) service端儘量簡單(簡單到只須要popen執行一條指令便可,有時間我再專門寫一篇博客講講這個項目的rpc是如何實現的),咱們選擇了讓web端直接等待處理結果,那麼問題來了,如何保證用戶沒必要等待,又能保證任務準確的執行呢?php

簡單的rpc結構以下圖linux

以往在處理一些稍微耗時的操做,能夠經過優化代碼結構,優化數據庫操做次數,起一些線程來處理一些簡單的好比發郵件,生成大的壓縮文件,提取視頻縮量圖,服務器間互訪等等操做,來避免用戶在web頁面的等待。laravel

但如今這個操做顯然不能用以前的這些方法作,由於如今的操做哪怕只執行一次,都是很是耗時的,更況且可能須要處理的多是上百上千臺服務器。這是在線程層面很難作,要知道在響應請求的web進程中起一個線程來作的話,在響應完斷開tcp鏈接以後,這個進程極可能被kill掉,像Apache就是這樣,固然能夠經過配置改變apache的行爲,但顯然不太靠譜。git

更好的作法是在web服務器上起一個守護進程去作這個事情,那麼問題就在於如何建立守護進程了,好在laravel幫咱們考慮了這個事情。github

Laravel的隊列

laravel的隊列默認是以sync(同步)的方式來處理多個任務,這顯然不是咱們想要的。鑑於這個項目使用的是laravel4.1版本,我選擇了beanstalkd來實現異步處理多個任務。web

其中beanstalkd是一種比較專業的隊列服務驅動器,是一個常駐後臺服務,咱們能夠經過它提供的接口來把任務提交給它,由它建立的守護進程來執行隊列。shell

配置隊列執行環境

1.安裝beanstalkd服務

我開發的電腦爲CentOS5.4,版本比較低,因此裝的過程當中仍是遇到些麻煩數據庫

首先執行下面的指令apache

1 wget ftp://fr2.rpmfind.net/linux/epel/5/ppc/epel-release-5-4.noarch.rpm
2 rpm -ivh epel-release-5-4.noarch.rpm
3 yum makecache
4 yum search beanstalkd

但最後發現找不到這個軟件,因而將yum的源換成了163的json

1 cd /etc/yum.repos.d
2 wget http://mirrors.163.com/.help/CentOS5-Base-163.repo
3 mv CentOS-Base.repo CentOS-Base.repo.bak

再次makecache && install 就OK了,安裝完後啓動beanstalkd服務

1 service beanstalkd start

另外能夠搜索到beanstalkd的配置文件放在了sysconfig下

爲laravel添加beanstalkd的驅動

beanstalkd的php驅動包爲pda/pheanstalk

進入laravel的protected目錄,composer.json在這個目錄下

執行

1 composer require pda/pheanstalk 2.*

出現以下的錯誤

能夠看出鏡像地址響應 502,因此須要給composer找一個可用的鏡像 http://www.phpcomposer.com/

修改~/.composer/config.json以下

而後回到protected目錄,再執行前面安裝驅動的命令安裝,這回出現了不同的錯誤

上面的php包中,第一個是爲了phpstorm的對laravel更好的支持,後面一個symfony/yaml已經安裝,並不須要升級,因此修改composer.json,直接將這兩個項目刪除掉就好了

刪除完以後,再次執行安裝命令安裝

能夠看到終於成功了,能夠經過 composer show -i 查看安裝了哪些包

測試

在TestController中添加一個action

 1 class TestController extends BaseController
 2 {
 3   
 4     public function getQueue(){
 5         //
 6         Log::info("添加一個對列任務");
 7         Queue::push('SendEmail',array('message'=>'哈哈'));
 8         Log::info('任務添加完畢');
 9         exit;
10     }
11 
12 }

在app目錄下新建tasks目錄,並修改protected/composer.json和app/global.php,將這個目錄加到類加載路徑中

修改global.php

1 ClassLoader::addDirectories(array(
2 
3    app_path().'/commands',
4    app_path().'/controllers',
5    app_path().'/models',
6    app_path().'/database/seeds',
7    app_path().'/library',
8    app_path().'/tasks',
9 ));

修改composer.json

1 "classmap": [
2   "app/commands",
3   "app/controllers",
4   "app/models",
5   "app/database/migrations",
6   "app/database/seeds",
7   "app/tasks",
8   "app/tests/TestCase.php"
9 ]

之後的耗時調度任務的代碼就放在這個目錄下面了

首先新建一個BaseTask.php

 1 /**
 2  * Created by PhpStorm.
 3  * User: Administrator
 4  * Date: 2015/8/19 0019
 5  * Time: 11:55
 6  */
 7 abstract class BaseTask
 8 {
 9     public abstract  function fire($job,$data);
10 }

而後新建一個SendMail.php

 1 /**
 2  * Created by PhpStorm.
 3  * User: Administrator
 4  * Date: 2015/8/19 0019
 5  * Time: 11:50
 6  */
 7 class SendEmail extends BaseTask
 8 {
 9 
10     public function fire($job, $data)
11     {
12         // TODO: Implement fire() method.
13         Log::info("對列任務執行".json_encode($data)."Time : ".time());
14         sleep(30);
15         Log::info("對列任務執行完畢".time());
16         //  將任務從隊列衝刪除
17         $job->delete();
18         //  將任務返回到隊列
19 
20 //        $job->release();
21 
22     }
23 }
最後就是去修改config目錄下的配置文件queue.php文件了,修改成 
1 'default'   => 'beanstalkd',
1 'beanstalkd' => array(
2    'driver' => 'beanstalkd',
3    'host'   => 'localhost',
4    'queue'  => 'default',
5    'ttr'   =>    60,
6 ),
關於ttr的解釋這裏有一個
表示time to run ,這個參數能夠覆蓋默認參數讓Beanstalkd 檢測是否在這個時間內完成
至此配置和寫代碼完成,在shell中執行,要在protected目錄下,artisan文件在這個目錄下
1 php artisan queue:work
2 php artisan queue:listen

而後訪問url,http://192.168.1.10/ssanlv/test/queue,能夠發現請求立刻就完成了,頁面並無等待

查看protected/app/storage/logs/laravel.log 能夠看到下面的內容

508-478 恰好30秒

下面測試一個實際的問題,印象中apache服務器與客戶端在請求完成斷開鏈接後會kill掉負責處理的httpd進程,只有配置了keep-alive參數在會將進程保留到apache進程池中,因此,但用戶請求一個耗時操做以後,關閉了瀏覽器,這個處理耗時任務的守護進程會不會也被kill掉呢?固然,其實有點多慮了,當響應完成以後tcp連接已經被斷開掉了,若是進程會被kill掉,那麼早就kill掉了,跟你瀏覽器關沒關應該沒多大關係,仍是試試吧,實踐纔是硬道理

這裏將SendMial中的sleep時間改長一點,改成 600秒

最後發現沒有執行完,能夠看到listen報出異常

很顯然執行超時,看來是前面設置的ttr的問題

將ttr註釋掉或者修改掉更高的值,發現仍是不行,最後在仔細看看報錯信息,發現

因此改變命令的執行方式

1 php artisan queue:listen --timeout=800

最後命令任務成功執行完畢

能夠看到 1353-753 = 600 剛恰好

另外,看樣子 這個任務對列應該是被保存起來了,當我沒有啓動 listen時,任務怎麼都不會處理,但我一但啓動listen,前面添加的任務就會立馬執行

但最後仍是有個問題這個是對列形式進行處理,要啓動下一個對列任務,必須等上一個對列任務執行完畢,不過以前曾看到過,一個work對應一個任務隊列,那麼我徹底能夠起多個任務隊列,有點多核CPU的調度哦。

更好的辦法

最後,再跟一位大神討論了一下,探討出了另一個更加優秀的辦法,雖然會加劇節點上rpc service代碼的複雜度,不過也不是很麻煩。

這種方式就是回調,管理集羣的web服務器能夠不用等待,只需以下步驟,

  • 經過web服務器上的rpc client向要執行耗時操做的節點上的rpc service發送一條指令,
  • 節點上的rpc service收到指令後,不先執行指令,而是立刻向web服務器,也就是rpc client返回一個任務ID。
  • web服務器將這個id做爲一條任務記錄保存到數據庫。
  • 節點上的rpc service處理指令,至於處理指令,也是在節點上在單獨起一個進程 P 來處理,由於rpc service也不能讓rpc client傻等着
  • 處理進程 P 處理完了以後,將執行結果和任務ID做爲參數,回調web服務器的一個web接口
  • web服務器接到rpc service的回調以後,經過ID查找到任務,更新任務的執行狀態,更新數據

很顯然,這種方式更加可靠,也大大減輕了web 服務器的負擔,要知道Linux 系統的線程數是有限制的,但這要耗時任務多了,若是然服務器去等,無論啥策略都極可能吧服務器整垮。

相關文章
相關標籤/搜索