fshafiee/laravel-once

Laravel的任务汇总

1.1.0 2021-02-20 18:51 UTC

This package is auto-updated.

Last update: 2024-09-21 02:49:20 UTC


README

Build Status

Laravel Once Banner

此包允许将任务延迟到请求结束时执行。它还会汇总相同的任务,以确保每个请求或期望的时间窗口只处理一次。

入门

安装包

通过composer将包添加到您的项目中

composer require fshafiee/laravel-once

定义可滚动的任务

您需要做的是创建一个新的类,它扩展了 LaravelOnce\Tasks\AutoDispatchedTask,这是一个抽象类。您必须定义 __constructperform 方法。每次创建此可滚动类的实例时,它都会自动添加到待办列表中。

满足 perform 操作所需的依赖关系必须传递给 __construct 并分配给实例变量。

示例...

我们想处理 Author 对象的缓存重新验证。每个缓存的对象也包含在对象中的 Book。因此,作者及其书籍的任何更改都应触发缓存重新验证。还有一个API允许出版商批量添加或更新作者及其书籍。因此,很可能在很短的时间内触发缓存重新验证。以下是我们可以安排的代码

  1. 创建一个更新作者缓存的滚动任务。
namespace App\Jobs\Rollables;

use App\Jobs\UpdateAuthorCache;
use App\Models\Author;
use LaravelOnce\Tasks\AutoDispatchedTask;

class UpdateAuthorCacheOnce extends AutoDispatchedTask
{
    public $authorId;

    public function __construct(string $authorId)
    {
        /**
         * Make sure parent::_construct() method is called.
         * or else the task won't be automatically added
         * to the task backlog, and you'd need to add it manually
         * by resolve the service.
         */
        parent::__construct();
        $this->authorId = $authorId;
    }

    public function perform()
    {

        UpdateAuthorCache::revalidate($this->authorId);
        /**
         * You could also dispatch the job to a queue in
         * order to process it asynchronously. It'll be
         * dispatched only once at the end of the request.
         */
    }
}
  1. 在调用 perform 方法所封装的逻辑的地方实例化一个可滚动任务。

考虑到存在此单一副作用的单个订阅者

namespace App\Subscribers;

use App\Events\AuthorCreated;
use App\Events\AuthorUpdated;
use App\Events\BookCreated;
use App\Events\BookUpdated;
// ...

use App\Jobs\Rollables\UpdateAuthorCacheOnce;

class AuthorCacheSubscriber
{
     /**
     * Register the listeners for the subscriber.
     *
     * @param  Dispatcher  $events
     */
    public function subscribe($events)
    {
        $events->listen(AuthorCreated::class,   self::class.'@handle');
        $events->listen(AuthorUpdated::class,   self::class.'@handle');
        $events->listen(BookCreated::class,     self::class.'@handle');
        $events->listen(BookUpdated::class,     self::class.'@handle');
        // ... the rest of the event bindings
    }

    public function handle($event)
    {
        /**
         * Instead of:
         *   UpdateAuthorCache::revalidate($event->getAuthorId());
         * We have:
         */
        new UpdateAuthorCacheOnce($event->getAuthorId());
    }
}

如你所见,如果做得正确,可滚动任务可以被当作直接替换。

防抖任务

除了在一个请求的上下文中汇总类似的任务,您还可以在期望的时间窗口内跨不同的请求进行汇总。想象一下,当产品详细信息发生变化时,更新产品目录这样的重任务。而不是每次修改后都进行更新,您可以在第一次更新发生时立即调度一个 DebouncingTask,并设置期望的等待时间。如果在等待时间内用户进行了其他更新,计时器将重置。当等待时间到期时,任务将被执行。

namespace App\Jobs\Rollables;

use App\Jobs\UpdateProductsCatalogue;
use LaravelOnce\Tasks\DebouncingTask;

class UpdateUsersProductsCatalogue extends DebouncingTask
{
    public $userId;

    public function __construct(string $userId)
    {
        /**
         * Make sure parent::_construct() method is called.
         * or else the task won't be automatically added
         * to the task backlog, and you'd need to add it manually
         * by resolve the service.
         */
        parent::__construct();
        $this->userId = $userId;
    }

    public function perform()
    {

        UpdateProductsCatalogue::forUser($this->userId);
        /**
         * You could also dispatch the job to a queue in
         * order to process it asynchronously. It'll be
         * dispatched only once at the end of the debounce
         * wait time 
         */
    }
    
    public function wait() : int
    {
        return 900;
    }
}

注意:为了使用 DebouncingTask,您需要一个支持 delay 的活动队列连接。因此,sync 队列驱动程序与此功能不兼容。

注意事项

在幕后,每次创建 AutoDispatchedTask 的实例时,都会从容器中解析出 OnceService,使用 OnceService->add 方法将其自己的引用添加到待办列表中。然后,这些任务通过解析服务和调用 commit 方法在可终止的中间件中以FIFO方式处理。因此,在命令行环境中(其中不存在HTTP请求生命周期),应手动调用 OnceService->commit

resolve(OnceSerivce::class)->commit();

请注意,对于队列作业,已经处理了调用 commit。如果您检查 OnceServiceProvider,您会发现以下行

Queue::after(function (JobProcessed $event) {
    resolve(OnceService::class)->commit();
});

如果您决定不使用包服务提供程序,或者这些可滚动任务是在作业或HTTP请求的上下文之外生成的(例如,cron作业、ad hoc脚本等),则需要手动 commit 任务。

一些注意事项...

是什么促使开发这个包的?

在某些情况下,资源的初始化过程会创建和触摸许多其他相关资源。此外,在那些资源上设置监听器以触发和处理副作用是非常常见的。这种大量操作和细粒度事件管理的组合可能会引入一些问题。

  1. 这些事件中的一些可能产生相同的不希望出现的副作用。如果没有汇总阶段,应用资源就会浪费在重复的操作上。
  2. 在某些情况下,这些在操作早期触发的副作用可能访问未初始化的数据,这可能导致难以追踪的问题和错误。
  3. 这些副作用本身可能触发其他系统(例如 PubSub 系统)中的程序。我们不希望它们过早触发操作。

此外

  1. 静默事件并手动重新触发它们会搞乱代码库,并引入各种难以测试和维护的逻辑分支。
  2. 将操作排队并发送到后台不会解决上述任何问题。它只会提高API调用的感知性能(这是一个好的做法,应该被采纳,无论主题如何)。

这些观察结果要求有一个任务汇总管理器模式,以确保副作用仅在请求的上下文中处理一次。由于这些是“副作用”,它们不应影响操作的主要逻辑和响应,因此有可终止中间件

关于Laravel 8.x的独特工作流程如何?

虽然Laravel 8.x支持独特的工作流程,但它仍然不能满足我们的要求

  1. 它只关注队列中当前的内容,这对于慢速队列(消费者不足、延迟作业等)是有效的。
  2. 我们仍然可能处理尚未完全初始化的资源的工作流程。
  3. 它没有解决同步操作中的问题,因为只有排队操作才能从中受益,前提是在第一点中提到的。

还有队列驱动程序支持的问题。

这两个方法似乎互为补充,解决“有效一次处理”的类似但不同的方面。