Laravel 13 队列路由详解
摘要
Laravel 13 引入了 Queue::route() 方法,允许在中央位置定义特定任务的默认队列/连接路由规则。本文将深入讲解队列路由的使用,包括:
- 队列路由核心概念
- Queue::route() 基础用法
- 任务分类与路由策略
- 动态路由配置
- 与队列属性的配合
- 实战案例:多队列系统
本文适合希望优化队列管理的 Laravel 开发者。
1. 队列路由概述
1.1 传统方式的问题
在 Laravel 13 之前,任务队列配置分散在多个地方:
1 2 3 4 5 6 7 8 9
| class ProcessPodcast implements ShouldQueue { public $connection = 'redis'; public $queue = 'podcasts'; }
ProcessPodcast::dispatch()->onConnection('redis')->onQueue('podcasts');
|
这种方式的问题:
- 配置分散,难以统一管理
- 修改需要改动多处代码
- 难以实现动态路由
1.2 Queue::route() 的优势
1 2
| Queue::route(ProcessPodcast::class, connection: 'redis', queue: 'podcasts');
|
优势:
- 配置集中管理
- 易于修改和维护
- 支持动态路由
- 与属性配合使用
2. 基础用法
2.1 基本路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\Facades\Queue; use App\Jobs\ProcessPodcast; use App\Jobs\SendEmail; use App\Jobs\GenerateReport;
class AppServiceProvider extends ServiceProvider { public function boot(): void { Queue::route(ProcessPodcast::class, connection: 'redis', queue: 'podcasts'); Queue::route(SendEmail::class, connection: 'database', queue: 'emails'); Queue::route(GenerateReport::class, connection: 'redis', queue: 'reports'); } }
|
2.2 只指定队列
1
| Queue::route(ProcessPodcast::class, queue: 'podcasts');
|
2.3 只指定连接
1
| Queue::route(ProcessPodcast::class, connection: 'redis');
|
2.4 批量路由
1 2 3 4 5 6 7 8 9
| use App\Jobs\ProcessPodcast; use App\Jobs\ProcessVideo; use App\Jobs\ProcessAudio;
Queue::route([ ProcessPodcast::class, ProcessVideo::class, ProcessAudio::class, ], connection: 'redis', queue: 'media');
|
3. 路由策略
3.1 按优先级路由
1 2 3 4 5 6 7 8 9 10 11
| Queue::route(ProcessPayment::class, queue: 'high'); Queue::route(SendNotification::class, queue: 'high');
Queue::route(GenerateReport::class, queue: 'default'); Queue::route(ProcessImage::class, queue: 'default');
Queue::route(CleanupData::class, queue: 'low'); Queue::route(ArchiveLogs::class, queue: 'low');
|
3.2 按资源类型路由
1 2 3 4 5 6 7 8 9
| Queue::route(ProcessVideo::class, queue: 'cpu-intensive'); Queue::route(ProcessImage::class, queue: 'cpu-intensive'); Queue::route(GeneratePdf::class, queue: 'cpu-intensive');
Queue::route(SendEmail::class, queue: 'io-intensive'); Queue::route(SyncExternalApi::class, queue: 'io-intensive'); Queue::route(UploadFile::class, queue: 'io-intensive');
|
3.3 按连接类型路由
1 2 3 4 5 6 7 8
| Queue::route(ProcessPodcast::class, connection: 'redis', queue: 'podcasts');
Queue::route(ProcessOrder::class, connection: 'database', queue: 'orders');
Queue::route(ProcessExternalWebhook::class, connection: 'sqs', queue: 'webhooks');
|
4. 动态路由
4.1 基于条件路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| use App\Jobs\ProcessPodcast; use Illuminate\Support\Facades\Queue;
class AppServiceProvider extends ServiceProvider { public function boot(): void { Queue::route(ProcessPodcast::class, function ($job) { if ($job->isPriority()) { return ['connection' => 'redis', 'queue' => 'high']; } return ['connection' => 'redis', 'queue' => 'default']; }); } }
|
4.2 基于环境路由
1 2 3 4 5 6 7
| Queue::route(ProcessPodcast::class, function ($job) { if (app()->environment('production')) { return ['connection' => 'sqs', 'queue' => 'production-podcasts']; } return ['connection' => 'database', 'queue' => 'local-podcasts']; });
|
4.3 基于负载路由
1 2 3 4 5 6 7 8 9 10 11
| use Illuminate\Support\Facades\Cache;
Queue::route(ProcessVideo::class, function ($job) { $load = Cache::get('queue:load:cpu-intensive', 0); if ($load > 80) { return ['connection' => 'redis', 'queue' => 'cpu-intensive-overflow']; } return ['connection' => 'redis', 'queue' => 'cpu-intensive']; });
|
5. 与队列属性配合
5.1 属性优先级
1 2 3 4 5 6 7 8 9 10 11 12 13
| use Illuminate\Queue\Attributes\OnQueue; use Illuminate\Queue\Attributes\OnConnection;
#[OnConnection('database')] #[OnQueue('emails')] class SendEmail implements ShouldQueue { }
Queue::route(SendEmail::class, connection: 'redis', queue: 'priority-emails');
|
5.2 属性作为默认值
1 2 3 4 5 6 7 8
| #[OnQueue('default')] class ProcessPodcast implements ShouldQueue { }
Queue::route(ProcessPodcast::class, queue: 'priority-podcasts');
|
6. 实战案例
6.1 多租户队列系统
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\Facades\Queue; use App\Jobs\ProcessTenantTask;
class QueueServiceProvider extends ServiceProvider { public function boot(): void { Queue::route(ProcessTenantTask::class, function ($job) { $tenant = $job->getTenant(); return [ 'connection' => 'redis', 'queue' => "tenant-{$tenant->id}", ]; }); } }
|
6.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
| <?php
namespace App\Services;
use Illuminate\Support\Facades\Queue; use App\Jobs\ProcessOrder;
class OrderQueueService { public function configureRouting(): void { Queue::route(ProcessOrder::class, function ($job) { $order = $job->getOrder(); if ($order->customer->isVip()) { return ['queue' => 'vip-orders']; } if ($order->total > 10000) { return ['queue' => 'large-orders']; } return ['queue' => 'orders']; }); } }
|
6.3 监控与告警
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\Facades\Queue; use Illuminate\Support\Facades\Log;
class QueueMonitorServiceProvider extends ServiceProvider { public function boot(): void { Queue::routing(function ($job, $route) { Log::info('Job routed', [ 'job' => get_class($job), 'connection' => $route['connection'] ?? 'default', 'queue' => $route['queue'] ?? 'default', ]); }); } }
|
7. 配置文件
7.1 队列路由配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| return [ 'routing' => [ 'enabled' => true, 'rules' => [ \App\Jobs\ProcessPodcast::class => [ 'connection' => 'redis', 'queue' => 'podcasts', ], \App\Jobs\SendEmail::class => [ 'connection' => 'database', 'queue' => 'emails', ], ], ], ];
|
7.2 加载配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\Facades\Queue;
class QueueServiceProvider extends ServiceProvider { public function boot(): void { if (!config('queue.routing.enabled')) { return; } foreach (config('queue.routing.rules') as $job => $route) { Queue::route($job, $route); } } }
|
8. 测试
8.1 测试路由配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?php
namespace Tests\Feature;
use Tests\TestCase; use Illuminate\Support\Facades\Queue; use App\Jobs\ProcessPodcast;
class QueueRoutingTest extends TestCase { public function test_job_is_routed_correctly() { Queue::fake(); ProcessPodcast::dispatch($podcast); Queue::assertPushedOn('podcasts', ProcessPodcast::class); Queue::assertPushedWithConnection('redis', ProcessPodcast::class); } }
|
8.2 测试动态路由
1 2 3 4 5 6 7 8 9 10
| public function test_dynamic_routing() { Queue::fake(); $vipOrder = Order::factory()->vip()->create(); ProcessOrder::dispatch($vipOrder); Queue::assertPushedOn('vip-orders', ProcessOrder::class); }
|
9. 最佳实践
9.1 集中配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class QueueServiceProvider extends ServiceProvider { public function boot(): void { $this->configureMediaQueue(); $this->configureEmailQueue(); $this->configureReportQueue(); } private function configureMediaQueue(): void { Queue::route([ ProcessVideo::class, ProcessAudio::class, ProcessImage::class, ], connection: 'redis', queue: 'media'); } }
|
9.2 命名规范
1 2 3 4 5 6
| Queue::route(ProcessPodcast::class, queue: 'media-podcasts'); Queue::route(ProcessVideo::class, queue: 'media-videos');
Queue::route(ProcessPodcast::class, queue: 'q1');
|
9.3 监控与日志
1 2 3 4 5 6 7 8
| Queue::routing(function ($job, $route) { if (app()->environment('production')) { Log::channel('queue')->info('Job routed', [ 'job' => get_class($job), 'route' => $route, ]); } });
|
10. 总结
Laravel 13 的队列路由功能为队列管理提供了更灵活的方式:
- 集中配置:所有路由规则统一管理
- 动态路由:支持基于条件的路由决策
- 易于维护:修改路由无需改动任务类
- 与属性配合:灵活的优先级控制
通过本指南,您已经掌握了队列路由的核心用法,可以开始优化应用的队列管理了。
参考资料