Symfony Messenger 选择异步传输时,不要先问“哪个最快”,而要先看消息是否允许丢失、项目已经依赖什么基础设施,以及失败后是否需要人工重试。小型项目通常从 Doctrine 或 Redis 开始;已经有 RabbitMQ 集群、需要明确交换机和队列语义时,再选 AMQP;只需要在进程内立即处理,则使用 sync://,它并不是异步队列。
官方文档:https://symfony.com/doc/current/messenger.html
一个实用的默认判断是:已有关系型数据库且吞吐适中选 Doctrine;已有 Redis 且希望低延迟选 Redis;需要消息代理能力、独立队列和更强路由语义选 AMQP。无论选哪种异步 transport,都要同时设计 worker、重试和 failure transport。
先确定消息真正需要什么
Messenger 的核心是 sender、receiver 和 handler:路由命中的消息被 sender 写入 transport,worker 再从 receiver 取出并交给 handler。没有路由的消息会同步处理,因此“配置了 transport”不等于“所有消息自动异步”。
可以先用这三个问题缩小范围:
- 消息能否在请求结束后再处理?能,才进入异步 transport。
- 项目是否已有数据库、Redis 或 RabbitMQ 运维能力?优先复用已有能力。
- 失败消息是否需要保留并人工重试?需要,就配置 failure transport。
如果只是想在测试环境隔离真实队列,可以使用 in-memory://;如果要求调用后立刻得到处理结果,则显式使用 sync://。
按基础设施选择异步 transport
Doctrine transport 把消息放进数据库表,部署简单,适合已有数据库、消息量中等且希望少引入一个中间件的应用。它的代价是队列消费会与业务数据库共享资源,需要观察锁竞争、清理和 worker 数量。
Redis transport 适合已经运行 Redis、希望低延迟且消息模型相对直接的场景。它不是“用了 Redis 就天然可靠”:仍然要根据部署模式、持久化策略和故障恢复要求评估消息可恢复性。
AMQP transport 适合已经使用 RabbitMQ 或兼容 AMQP 的团队。交换机、队列、路由键、优先级和独立消费组都更明确,但运维对象更多,队列参数也必须与现有 broker 配置协同。
| 场景 | 优先考虑 | 主要代价 |
|---|---|---|
| 已有数据库,吞吐适中 | Doctrine | 共享数据库资源,需管理消费压力 |
| 已有 Redis,追求低延迟 | Redis | 需单独评估持久化与故障恢复 |
| 已有 RabbitMQ,需要队列路由 | AMQP | broker 与队列拓扑更复杂 |
用 transport 名称连接路由与 worker
transport 的名字是应用侧契约,DSN 才是具体实现。下面示例把通知消息路由到名为 async 的 Doctrine 队列;代码中的注释说明了每个关键配置的作用。
# config/packages/messenger.yaml
framework:
messenger:
transports:
# 复用默认数据库连接,适合已有 Doctrine 的中等吞吐场景
async: 'doctrine://default?queue_name=async'
# 最终失败的消息保留在独立队列,便于查看和人工重试
failed: 'doctrine://default?queue_name=failed'
failure_transport: failed
routing:
# 只有匹配到路由的消息才会进入异步 transport
'App\\Message\\SendNotification': async
生产环境再运行 php bin/console messenger:consume async -vv 启动 worker。对延迟敏感、失败影响不同的消息,不要强行共用一个 transport:官方文档建议按延迟、失败模式或重试窗口拆分队列和 worker。
把重试和失败处理当成传输选择的一部分
异步消息最容易出问题的地方不是 DSN,而是失败后的状态。默认重试次数与延迟可以按 transport 配置;外部 API 限流通常需要更长的重试窗口,永久性参数错误则不应该无限重试。
# config/packages/messenger.yaml
framework:
messenger:
transports:
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
retry_strategy:
# 临时故障最多重试三次,第一次等待一秒
max_retries: 3
delay: 1000
# 后续等待逐次翻倍,避免集中重试压垮依赖
multiplier: 2
# 给重试等待设置上限,0 表示不限制
max_delay: 0
failure_transport: failed
如果不同消息的重要性不同,可以为特定 transport 配置独立的 failure transport。消费失败后使用 messenger:failed:show 查看,确认依赖恢复且消息仍然有效后,再用 messenger:failed:retry 选择性重试;不要把“自动重试次数”误当成业务幂等保证。
最后用一张决策表落地
- 不需要异步:使用默认同步处理或显式
sync://。 - 已有数据库且消息量适中:选择 Doctrine,先拆出独立队列名。
- 已有 Redis 且追求低延迟:选择 Redis,并把可靠性要求写进部署方案。
- 需要交换机、路由键、优先级或独立 broker:选择 AMQP。
- 对重要消息配置 failure transport,并为每类失败定义重试窗口。
常见问题:
- 配置 transport 后为什么还同步执行?检查 routing 是否匹配消息类;未匹配的消息仍会立即处理。
- Doctrine 和 Redis 该怎么选?优先复用已有基础设施,再用吞吐、延迟和恢复要求做取舍。
- 失败 transport 是否必须配置?不是必须,但没有它时消息达到重试上限后可能被丢弃,重要业务通常应保留。