先说下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/>'); } }