Symfony Mailer 不支持原生负载均衡,需通过 X-Transport 头手动控制传输选择,业务层实现轮询、故障转移与限流逻辑,Mailer 仅按头匹配传输且不参与分发决策。

Mailer 不支持原生负载均衡,得靠 X-Transport + 外部调度
Symfony Mailer 本身没有轮询、权重或自动故障转移机制。所谓“多个 SMTP 实现负载均衡”,实际是靠你手动控制哪封邮件走哪个传输——Mailer 只负责识别 X-Transport 头并切换,不参与分发逻辑。
常见误解是配了 main 和 backup 就能自动分流,但事实是:不加头就永远只走第一个(main),加了头才走指定的。真正的负载逻辑必须由业务代码实现。
- 不要指望
transports列表顺序或命名暗示优先级或轮询规则 - Mailer 不会检查某个传输是否超时/失败后自动切到下一个;它只按头字面值匹配,匹配不到就 fallback 到默认(通常是第一个)
- 若需故障转移,得自己封装一层:捕获
TransportExceptionInterface,重试时手动改X-Transport头再 send
配置多个传输时 DSN 必须互斥且可独立验证
每个传输对应一个完整、自洽的 DSN,不能共用账号或端口冲突,否则调试时会互相污染。
- Gmail 传输必须带
&auth_mode=login,SendGrid 传输不能带这个参数,混用会导致其中一个静默失败 - 避免用相同域名+端口配两个传输(比如都写
smtp://user@smtp.gmail.com:587),Mailer 不报错,但你无法区分日志来源 - 本地开发建议用不同端口模拟:一个配
smtp://localhost:1025(MailHog),另一个配sendmail://default,便于肉眼确认走的是哪个链路 - 运行
php bin/console debug:config mailer,确保输出中每个 transport 的 DSN 都是非空且格式合法,出现null://说明环境变量没加载或 YAML 键名拼错
发送时动态选传输:X-Transport 头必须在 send() 前设置
X-Transport 是唯一被 Mailer 识别的传输切换信号,但它不是“请求头”,而是 Mime 头,必须在构建 Email 对象阶段注入,且不能拼错大小写或空格。
- 正确写法:
$email->getHeaders()->addTextHeader('X-Transport', 'important'); - 错误写法:
$email->header('X-Transport', 'important')(Email 类无此方法)、addTextHeader('x-transport', ...)(Mailer 区分大小写) - 这个头不会出现在最终发出的邮件里,Mailer 发送前会自动剥离,所以收件人看不到
- 别在事件监听器(如
MessageEvents::MESSAGE_SENT)里加这个头——太晚了,发送流程已结束
负载逻辑要自己写,别依赖配置文件
真要实现“每 3 封通知走 Gmail、2 封走 SendGrid”,就得在业务层维护计数器或查数据库状态,而不是指望 YAML 文件能算数学题。
- 示例思路:定义一个
TransportRouter服务,接收$email,根据当前时间戳、用户 ID 哈希或 Redis 计数器返回应设的 transport 名(如'gmail'或'sendgrid'),再调用addTextHeader('X-Transport', $name) - 若某 SMTP 服务商限流(如 Gmail 每天 500 封),得自己记录发送量并主动降级,Mailer 不提供配额管理
- 注意并发安全:多个请求同时修改同一个计数器时,要用 Redis INCR 或数据库行锁,PHP 进程内 static 变量不可靠
最易被忽略的一点:Mailer 的 transport 切换只发生在发送瞬间,它不感知连接池、不缓存连接状态。每次 send() 都可能新建 TCP 连接——所谓“均衡”,本质是你控制每封邮件的出口,而非连接复用层面的调度。


















