请教关于异步事件任务的唯一的问题

在文档中,队列那章里面,Unique Jobs曾提到:文档链接

  1. 如果任务的另一个实例已经在队列中并且尚未完成处理,则不会分派该任务。
  2. 在现有任务完成处理之前,任何具有相同 product ID 的任务都将被忽略。

说明一下,我的事件驱动是基于redis,缓存驱动支持原子锁,我用一个简单的例子来提问:


事件示例:

  1. EventServiceProvider添加事件

         'App\Events\Test\RollEvent' => [
             'App\Listeners\Test\CapterBEventListener',
         ],
  2. RollEvent默认配置,不做说明

  3. CapterBEventListener为异步侦听器,为了测试uniqueId统一设置为5,并在执行过程中阻塞10秒钟

    <?php
    namespace App\Listeners\Test;
    use App\Events\Test\RollEvent;
    use Illuminate\Contracts\Queue\ShouldBeUnique;
    use Illuminate\Contracts\Queue\ShouldQueue;
    use Illuminate\Queue\InteractsWithQueue;
    class CapterBEventListener implements ShouldQueue, ShouldBeUnique
    {
     use InteractsWithQueue;
    
     private $_data = 0;
    
     /**
      * Create the event listener.
      *
      * @return void
      */
     public function __construct()
     {
         //
     }
    
     /**
      * Handle the event.
      *
      * @param  RollEvent  $event
      * @return void
      */
     public function handle(RollEvent $event)
     {
         echo 'RoolEventB===',PHP_EOL;
         sleep(10);
         echo '阻塞结束====',PHP_EOL;
     }
    
     public function shouldQueue(RollEvent $event) 
     {
         return true;
     }
    
     public function uniqid(RollEvent $event) 
     {
         return 5;
     }
    }

测试事件:

$ php artisan tinker
>>> $evet1 = new App\Events\Test\RollEvent(5)
>>> $evet2 = new App\Events\Test\RollEvent(5)
>>> event($evet1)
>>> event($evet2)

测试结果:

  • 还是按照顺序执行了两遍

  • 我的理解是在事件唯一ID中,第一个事件没有执行前,添加相同的ID事件,将不会执行

    > [2022-04-30 18:29:17][VlGCdjmgniuAKbFuJ6QL7t7jLuhxzEN6] Processing: App\Listeners\Test\CapterBEventListener 
    > RoolEventB===
    > 阻塞结束
    > [2022-04-30 18:29:27][VlGCdjmgniuAKbFuJ6QL7t7jLuhxzEN6] Processed: App\Listeners\Test\CapterBEventListener
    > [2022-04-30 18:29:27][uh4p7EuDmVSVEf17SrbcoUdKNQ1xVKzo] Processing: App\Listeners\Test\CapterBEventListener
    > RoolEventB===
    > 阻塞结束
    > [2022-04-30 18:29:37][uh4p7EuDmVSVEf17SrbcoUdKNQ1xVKzo] Processed: App\Listeners\Test\CapterBEventListener

    请问事件ID指的是什么唯一?为何相同编号却执行了两遍?

    我将事件侦听器改为任务,测试成功了

    <?php
    namespace App\Jobs;
    use Illuminate\Bus\Queueable;
    use Illuminate\Contracts\Queue\ShouldBeUnique;
    use Illuminate\Contracts\Queue\ShouldQueue;
    use Illuminate\Foundation\Bus\Dispatchable;
    use Illuminate\Queue\InteractsWithQueue;
    use Illuminate\Queue\SerializesModels;
    class TestPodcast implements ShouldQueue, ShouldBeUnique
    {
     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
    
     /**
      * Create a new job instance.
      *
      * @return void
      */
     public function __construct()
     {
         //
     }
    
     /**
      * Execute the job.
      *
      * @return void
      */
     public function handle()
     {
         echo 'handle start', PHP_EOL;
         sleep(10);
         echo 'handle end', PHP_EOL;
     }
    
     public function uniqueId() 
     {
         return 5;
     }
    }

    连续执行,只commit一次

    >>> App\Jobs\TestPodcast::dispatch();
    >>> App\Jobs\TestPodcast::dispatch();

    请问为什么在事件侦听器上不行呢?

《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 11

难道文档不是说job,虽然你都继承ShouldBeUnique,但是框架应该没有调用他的代码。 重复任务检查这个框架也是设计的怪怪的,我都生成唯一id了,为什么还需要投递进入队列由队列系统过滤?自己过滤难道性能不是更好吗?

1年前 评论
godruoyi

很高兴你能提这个问题,我看了下源码,确实,用 event 触发的任务,是没法「享受」Unique Jobs 这一特权的。

event 也就是用事件的形式,即使你 implements ShouldQueue, ShouldBeUnique 也没用,因为在投递任务到队列服务时,根本就没有检查这个特性。

而用 job::distatch 的方式就不一样了,参见:

    protected function shouldDispatch()
    {
        if (! $this->job instanceof ShouldBeUnique) {
            return true;
        }

        return (new UniqueLock(Container::getInstance()->make(Cache::class)))
                    ->acquire($this->job);
    }

不过你可以试试 dispatch($event) 说不定就 OK 了。

1年前 评论
godruoyi (作者) 1年前
godruoyi (作者) 1年前
MArtian 1年前

抱歉同学,按照 @godruoyi 老师的说法,确实 dispatch 和 event 区别很大。

dispatch() 中 shouldQueue 和 shouldUnique 方法在并发情况下都不会发生超发队列的情况,但是 event() 就不行,前几天我解答的另一个帖子写错了。

但问题好像并没有那么简单,我又测试了 $this->dispatch(),也是会发生超发的情况。

还有就是像你上面所写:

App\Jobs\TestPodcast::dispatch();

在并发下也不会发生超发的问题。


到此位置先总结一下:

目前已知 helper 中的 event() 方法,控制器中的 $this->dispatch() 会出现 shouldQueue 和 shouldUnique 判断失效的问题,导致队列超发。

而 Job 本身的静态方法 Job::dispatch() 和 helper 中的 dispatch() 不会出现队列超发的问题。

因为 Job::dispatch()dispatch() 都是使用的 Dispatchable 这个 trait 中的 dispatch() 方法,这个方法调用了

public static function dispatch(...$arguments)
{
    return new PendingDispatch(new static(...$arguments));
}
\vendor\laravel\framework\src\Illuminate\Foundation\Bus\PendingDispatch.php

在这个文件中,找到 shouldDispatch() 方法,就能看见 @godruoyi 老师上面所说的对 shouldUniqueId 这个 trait 的判断。

我带着疑问去翻看了一眼 PR ,发现在去年已经有人提过类似的问题,不过官方到现在还没有解决。

ShouldBeUnique doesn’t work with event listeners

file

但控制器中的 $this->dispatch(),它和 event() 方法一样,都是直接分发了队列,没有走上面的那个 trait 检查。

然后我发现,这个 dispatch 其实分两种触发方式,一种是 Dispatchable 这个 trait,但凡是通过这个 trait 分发出去的队列,是不会超发的,而通过 Dispatcher 这个类分发出去的队列,shouldQueue 和 shouldUnique 就会失效。

那么现在我有一个问题,是哪里的代码影响了 shouldQueue 的判断?

我目前找到的相关代码只有这部分

// \vendor\laravel\framework\src\Illuminate\Bus\Dispatcher.php

/**
 * Determine if the given command should be queued. * * @param mixed $command
  * @return bool
 */protected function commandShouldBeQueued($command)
{
  return $command instanceof ShouldQueue;
}

今天先这样,太晚了,明天再来继续研究。

1年前 评论
MArtian (作者) 1年前
godruoyi 1年前
MArtian (作者) 1年前
godruoyi 1年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!