问题

一个Task没有允许结束,另一个Task又起了,这固然会造成程序执行的结果不是我们想要的,为了解决这个问题我发现Laravel有提供现成的方法。

方案

方法一:可以在代码中使用原子锁,这样即使第二个tash进程启动了,也可以阻挡住。

方法二:使用Laravel提供现成的withoutOverlapping方法,官方案例:$schedule->command('emails:send')->withoutOverlapping();

方法一中使用哪一种的锁机制,那就看自家自己的喜好了不分好坏,只分性能高低😼,个人使用redis原子锁,从2.6.12版本以后,使用锁的方式变得更为简单,如设置一个名为lock的5秒钟原子锁:SET lock 1 EX 5 NXSET 更多参数用法

方法二我的测试结果如下:

  1. 创建测试command文件,命令:php artisan make:command Test

  2. 代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Test extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'test:over';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Command description';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        $this->line('Start');
        \Log::info('command run time:'.date('Y-m-d H:i:s'));
        sleep(90);
        $this->line('End');
    }
}
  1. Kernel.php代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
protected $commands = [
    ...
    ...
    Commands\Test::class
]

protected function schedule(Schedule $schedule)
{
    ...
    ...
    $schedule->command('test:over')->everyMinute()->withoutOverlapping();
}
  1. 测试结果
1
2
3
4
5
6
7
8
9
[2020-03-09 13:00:02] production.INFO: command run time:2020-03-09 13:00:02  
[2020-03-09 13:02:00] production.INFO: command run time:2020-03-09 13:02:00  
[2020-03-09 13:04:02] production.INFO: command run time:2020-03-09 13:04:02  
[2020-03-09 13:06:01] production.INFO: command run time:2020-03-09 13:06:01  
[2020-03-09 13:08:02] production.INFO: command run time:2020-03-09 13:08:02  
[2020-03-09 13:10:01] production.INFO: command run time:2020-03-09 13:10:01  
[2020-03-09 13:12:02] production.INFO: command run time:2020-03-09 13:12:02  
[2020-03-09 13:14:01] production.INFO: command run time:2020-03-09 13:14:01  
[2020-03-09 13:16:02] production.INFO: command run time:2020-03-09 13:16:02  

只要不人为干预强制启动多个schedule:run,不会有问题。

在19:32秒时强制,人为强制启动多个schedule:run进程,log如下:

1
2
3
4
5
6
[2020-03-09 13:18:01] production.INFO: command run time:2020-03-09 13:18:01  
[2020-03-09 13:19:32] production.INFO: command run time:2020-03-09 13:19:32  
[2020-03-09 13:22:02] production.INFO: command run time:2020-03-09 13:22:02  
[2020-03-09 13:24:01] production.INFO: command run time:2020-03-09 13:24:01  
[2020-03-09 13:26:02] production.INFO: command run time:2020-03-09 13:26:02  
[2020-03-09 13:28:01] production.INFO: command run time:2020-03-09 13:28:01 

原理分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    /**
     * Do not allow the event to overlap each other.
     *
     * @return $this
     */
    public function withoutOverlapping()
    {
        $this->withoutOverlapping = true;

        return $this->skip(function () {
            return file_exists($this->mutexPath());
        });
    }
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    /**
     * Register a callback to further filter the schedule.
     *
     * @param  \Closure  $callback
     * @return $this
     */
    public function skip(Closure $callback)
    {
        $this->rejects[] = $callback;

        return $this;
    }
1
2
3
4
5
6
7
8
9
    /**
     * Get the mutex path for the scheduled command.
     *
     * @return string
     */
    protected function mutexPath()
    {
        return storage_path('framework'.DIRECTORY_SEPARATOR.'schedule-'.sha1($this->expression.$this->command));
    }
1
2
3
➜   ls storage/framework
cache  schedule-7d2b691b1562e375e4f787cfd391d7a019e5f8cf  sessions  views
➜  ✗ cat storage/framework/schedule-7d2b691b1562e375e4f787cfd391d7a019e5f8cf

参考