fshafiee / laravel-once
Laravel的任务汇总
Requires
- php: ^7.1
- ext-json: *
Requires (Dev)
- nunomaduro/larastan: ^0.6.11
- orchestra/testbench: 4.*
- phpstan/phpstan: ^0.12
- phpunit/phpunit: ^8.0
- squizlabs/php_codesniffer: 3.*
This package is auto-updated.
Last update: 2024-09-21 02:49:20 UTC
README
此包允许将任务延迟到请求结束时执行。它还会汇总相同的任务,以确保每个请求或期望的时间窗口只处理一次。
入门
安装包
通过composer将包添加到您的项目中
composer require fshafiee/laravel-once
定义可滚动的任务
您需要做的是创建一个新的类,它扩展了 LaravelOnce\Tasks\AutoDispatchedTask
,这是一个抽象类。您必须定义 __construct
和 perform
方法。每次创建此可滚动类的实例时,它都会自动添加到待办列表中。
满足 perform
操作所需的依赖关系必须传递给 __construct
并分配给实例变量。
示例...
我们想处理 Author 对象的缓存重新验证。每个缓存的对象也包含在对象中的 Book。因此,作者及其书籍的任何更改都应触发缓存重新验证。还有一个API允许出版商批量添加或更新作者及其书籍。因此,很可能在很短的时间内触发缓存重新验证。以下是我们可以安排的代码
- 创建一个更新作者缓存的滚动任务。
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. */ } }
- 在调用
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
任务。
一些注意事项...
是什么促使开发这个包的?
在某些情况下,资源的初始化过程会创建和触摸许多其他相关资源。此外,在那些资源上设置监听器以触发和处理副作用是非常常见的。这种大量操作和细粒度事件管理的组合可能会引入一些问题。
- 这些事件中的一些可能产生相同的不希望出现的副作用。如果没有汇总阶段,应用资源就会浪费在重复的操作上。
- 在某些情况下,这些在操作早期触发的副作用可能访问未初始化的数据,这可能导致难以追踪的问题和错误。
- 这些副作用本身可能触发其他系统(例如 PubSub 系统)中的程序。我们不希望它们过早触发操作。
此外
- 静默事件并手动重新触发它们会搞乱代码库,并引入各种难以测试和维护的逻辑分支。
- 将操作排队并发送到后台不会解决上述任何问题。它只会提高API调用的感知性能(这是一个好的做法,应该被采纳,无论主题如何)。
这些观察结果要求有一个任务汇总管理器模式,以确保副作用仅在请求的上下文中处理一次。由于这些是“副作用”,它们不应影响操作的主要逻辑和响应,因此有可终止中间件。
关于Laravel 8.x的独特工作流程如何?
虽然Laravel 8.x支持独特的工作流程,但它仍然不能满足我们的要求
- 它只关注队列中当前的内容,这对于慢速队列(消费者不足、延迟作业等)是有效的。
- 我们仍然可能处理尚未完全初始化的资源的工作流程。
- 它没有解决同步操作中的问题,因为只有排队操作才能从中受益,前提是在第一点中提到的。
还有队列驱动程序支持的问题。
这两个方法似乎互为补充,解决“有效一次处理”的类似但不同的方面。