分布式调度如何实现 分布式调度框架选择
在全局系统架构中,将不同的功能模块拆分为独立的应用程序或服务是常见的做法,例如将前端Web应用与远程批处理/作业应用分离。这种分离有助于独立扩展、安全发布和降低连接度。然而,当Web应用需要触发交互服务执行某些异步任务时,如何有效地利用Laravel的团队传统的 Laravel 队列预设通常发送和接收任务的应用程序是同一个,这使得在不同的代码库和应用实例之间直接调度任务变得复杂。面临的挑战
当 web 应用和队列处理应用运行在不同的服务器上,并且拥有独立的 laravel 项目时,直接通过 web 应用 派遣() 一个任务,让耳机批处理应用的队列工作来处理,似乎是不方便的。最初的考量可能使用redis的pub/sub机制,结合laravel队列在队列处理。但这种方法引入了额外的复杂性,例如pub/sub订阅者在部署时可能会丢失消息,并且需要额外的supervisor进程来管理订阅。更理想的方案是,直接利用laravel现有的队列机制,实现跨包括所有任务调度。解决方案:跨应用任务类结构同步经过实践验证,一个简洁而有效的解决方案是:在发送任务的Web应用和处理任务的物料批处理应用中,定义结构完全一致的Job(任务)类。关键在于,Web应用中的Job类只需定义构造函数和属性,而handle()方法可以为空或仅作占位;而批处理应用中的Job类则需要包含实际的业务逻辑实现。
以下是具体的实现示例:
1. Web应用(任务发送方)
在Web应用的app/Jobs目录下创建SomeJob.php:lt;?phpnamespace App\Jobs;use Illuminate\Bus\Queueable;use Illuminate\Contracts\Queue\ShouldQueue;use Illuminate\Foundation\Bus\Dispatchable;use Illuminate\Queue\InteractsWithQueue;use Illuminate\Queue\SerializesModels;class SomeJob Implements ShouldQueue{ use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; private $userId; private $someParam; /** *一个新的任务实例。 * * @param int $userId * @param string $someParam * @return void */ public function __construct(int $userId, string $someParam) { $this-gt;userId = $userId; $this-gt;someParam = $someParam; } /** * 执行任务。
(此处的handle方法通常为空或仅作占位) * 实际的业务逻辑将在批处理应用中实现。 * * @return void */ public function handle() { // 实际的业务逻辑将在批处理应用中实现 }}登录后复制
在Web应用中调度任务:lt;?phpuse App\Jobs\SomeJob;$userId = 123;$someParam = 'example_data';SomeJob::dispatch($userId, $someParam);登录后复制
当SomeJob::dispatch() 被调用时,Laravel 粘贴任务的类名(包括命名空间)、构造函数参数以及必要的元数据序列化,将其存储到配置的队列驱动中(例如Redis)。
2. 课堂批处理应用(任务处理方)
在课堂批处理应用的 app/Jobs 目录下创建 SomeJob.php,确保命名空间、类名和构造函数与 Web 应用中的 Job 类一致:lt;?phpnamespace App\Jobs;use Illuminate\Bus\Queueable 完全;use Illuminate\Contracts\Queue\ShouldQueue;use Illuminate\Foundation\Bus\Dispatchable;use Illuminate\Queue\InteractsWithQueue;use Illuminate\Queue\SerializesModels;class SomeJob Implements ShouldQueue{ use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; private $userId; private $someParam; /** * 创建一个新的任务实例。 * * @param int $userId * @param string $someParam * @return void */ public function __construct(int $userId, string $一些参数){ $this-gt;userId = $userId; $this-gt;someParam = $someParam; } /** * 执行任务。
(此处包含实际的业务逻辑) * * @return void */ public function handle() { // 实际的业务逻辑实现 echo quot;处理用户 ID: quot; . $this-gt;userId 。 quot; 参数: quot; . $this-gt;someParam . quot;\nquot;; // 如:更新数据库、调用外部API等 }}登录后复制
在队列批处理应用中,启动队列工作器来监听并处理任务:php artisan queue:work --sleep=3 --tries=1 --delay=1登录后复制原理详解
当Web应用调度SomeJob时,Laravel继承任务的类名(例如App\Jobs\SomeJob)、构造函数参数($userId, $someParam)以及其他必要的元数据序列化后存储到Redis队列中。
当批次处理应用的队列工作器从Redis中取出这个时,它会根据存储的类名(App\Jobs\SomeJob)尝试实例化一个本地的App\Jobs\SomeJob对象。由于两个应用中的SomeJob 类定义(包括命名空间、类名和构造函数)是相同的,Laravel 能够成功地实例化该对象,并把队列中存储的参数注入到构造函数中。然后,工作器会调用这个本地实例的 handle() 方法,从而执行批处理应用中定义的业务实际逻辑。优势与注意事项了解高度连接:Web 应用接驳批处理应用的具体业务逻辑,它只负责将任务的“含义”和必要的参数发送出去。可以独立部署、升级和两个扩展,互不影响。版本兼容:这种机制甚至可以在Web应用和批处理应用使用不同Laravel版本的情况下工作(例如,一个Laravel 8,一个Laravel 5.7),因为核心的序列化/反序列化机制和Job类的结构是兼容的。利用现有队列系统:引入额外的Pub/Sub层,直接利用Laravel强大的队列功能,如重试、失败任务处理等。共享队列驱动: 确保应用配置使用同一个队列驱动两个(例如Redis)和同一个队列连接/名称。
事项注意:Job类结构必须一致:App\Jobs\SomeJob的命名空间、类名以及构造函数的签名(参数类型和顺序)在两个应用中必须完全一致。任何不一致都可能导致任务反序列化失败。输入的数据类型:首先提交基本数据类型(整数、字符串、布尔值)或简单提交。如果需要提交复杂的Eloquent模型实例,请确保模型在两个应用中都存在且结构一致,或者仅提交模型的ID,让处理方根据ID重新查询。依赖管理:handle()方法中使用的任何服务或类都必须在队列批处理应用中可用,并通过依赖注入或服务容器解析。总结
通过在Web应用和队列批处理应用中同步Job类的结构(但只在队列实现handle()方法),并共享一个队列队列(如Redis),我们能够顺利地实现跨Laravel应用的异步任务调度。
充分利用了Laravel队列的强大功能,简化了这样的全局系统中的任务管理,同时保留了各个服务间的松散连接,是构建可伸缩、可维护的各个Laravel应用的推荐方法。
以上文章就是在Laravel应用中实现跨服务队列调度的详细信息,更多请关注乐哥常识网其他相关模式!