先說下bug背景,以下是調用鏈:
php版本7.1
laraavel版本5.5
說明下運行流程A:通過調用TestC artisan command或者接口用dispatch方法將TestCJob放入異步隊列,TestCJob通過handle方法調用Artisan::call運行TestCItem,最后這個TestCItem在構造方法中初始化一些變量如runtime,在運行時打印runtime
注意注意注意,這里就出現問題了
首先重啟隊列(讓Job生效)
第一種使用場景:運行流程A的時候,TestCItem handel的時候打印的runtime是重啟隊列的時間,以后的每個Job調用TestCItem的handle都是打印的重啟隊列的時間
第二種使用場景:直接通過命令行執行TestCItem時handle,但是這時打印的是執行時間
以下是運行日志:
重啟Job隊列時會打印一次,之后再執行流程A就不會進入構造函數且runtime是構造函數時間
直接運行TestCItem命令行則每次都會進入TestCItem的構造函數
這就會造成問題在流程A中runtime和一些其他的在構造函數里設置的值每次運行不是最新的,
回想下Laravel的架構就會發現問題所在:
這里Artisan::call向laravel的容器中注入了一個Artisan Command,所以觸發了構造函數,之后每次運行時就直接使用注入的對象,所以之前初始化的runtime就是對象構造時候的時間
所以在Laravel中組合使用Job隊列和ArtisanCommand時要注意,千萬不要使用構造函數賦值(你將得到Job隊列重啟時的賦值),應該在handle中賦值使用。
以下是各個類:
TestC
<?php namespace App\Console\Commands; use App\Jobs\TestCJob; use Illuminate\Console\Command; class TestC extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'testc'; /** * The console command description. * * @var string */ protected $description = 'testc'; /** * Create a new command instance. * * @return void */ public function __construct() { parent::__construct(); } /** * Execute the console command. * * @return mixed */ public function handle() { $this->output('---腳本開始---'); dispatch(new TestCJob('xxxxx'))->onConnection('redis'); $this->output('---腳本結束---'); } /** * @param $msg * @param bool $showTime * @param bool $isCli */ public function output($msg, $showTime = true, $isCli = true) { echo ($showTime ? date('Y-m-d H:i:s') . ' ' : '') . $msg . ($isCli ? PHP_EOL : '<br/>'); } }
TestCJob
<?php namespace App\Jobs; use Exception; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Artisan; class TestCJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; /** * 任務可以嘗試的最大次數。 * * @var int */ public $tries = 3; /** * 任務可以執行的最大秒數 (超時時間)。 * * @var int */ public $timeout = 120; /** * Create a new job instance. * * @return void */ public function __construct() { } /** * Execute the job. * * @return void */ public function handle() { $re = 1; $re = Artisan::call('testcitem'); file_put_contents('testcitem.log', '$re' . $re . "\n", 8); } /** * 任務失敗的處理過程 * * @param Exception $exception * @return void */ public function failed(Exception $exception) { file_put_contents('testcitemfail.log', json_encode($exception->getMessage()) . "\n", 8); // 給用戶發送任務失敗的通知,等等…… } }
TestCItem
<?php namespace App\Console\Commands; use Illuminate\Console\Command; class TestCItem extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'testcitem'; /** * The console command description. * * @var string */ protected $description = 'testcitem'; /** * Create a new command instance. * * @return void */ public function __construct() { parent::__construct(); $this->runtime = date('Y-m-d H:i:s'); $this->output('---TestCItem---__construct'); } private $runtime = 0; /** * Execute the console command. * * @return mixed */ public function handle() { $this->output('---handle---' . $this->runtime); return 0; } /** * @param $msg * @param bool $showTime * @param bool $isCli */ public function output($msg, $showTime = true, $isCli = true) { echo ($showTime ? date('Y-m-d H:i:s') . ' ' : '') . $msg . ($isCli ? PHP_EOL : '<br/>'); } }