在PHP项目里,邮件群发如果放在用户请求过程中同步执行,往往会因为连接SMTP服务器缓慢、收件人数量多而导致脚本超时,甚至拖垮整个站点。使用命令模式把邮件发送封装为命令对象,再配合消息队列做异步消费,是比较成熟的优化思路。

为什么要用命令模式
命令模式将“发送邮件”这个动作抽象成一个对象,调用方只需要创建并抛出命令,不需要关心具体怎么发。这样做有几个好处:
- 发送逻辑和触发逻辑解耦,代码更清晰
- 命令对象可以序列化后丢进队列,实现异步
- 方便做重试、日志和权限控制
定义邮件发送命令
下面用一个简单的PHP类来表示邮件命令,它实现了通用的Command接口。
<?php
interface Command {
public function execute();
}
class SendEmailCommand implements Command {
private $to;
private $subject;
private $body;
public function __construct($to, $subject, $body) {
$this->to = $to;
$this->subject = $subject;
$this->body = $body;
}
// 执行具体的邮件发送
public function execute() {
// 这里调用实际邮件库,如PHPMailer
echo "发送邮件给 {$this->to}: {$this->subject}n";
}
// 转为数组,方便存入队列
public function toArray() {
return [
'to' => $this->to,
'subject' => $this->subject,
'body' => $this->body
];
}
// 从数组还原命令
public static function fromArray($data) {
return new self($data['to'], $data['subject'], $data['body']);
}
}
?>把命令放进消息队列
我们可以用Redis的list结构作为简易消息队列,把命令序列化后推入。生产代码只负责入队,不负责发送。
<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$cmd = new SendEmailCommand('user@ipipp.com', '欢迎', '感谢注册');
$redis->rPush('email_queue', serialize($cmd->toArray()));
echo '已加入发送队列';
?>队列消费者处理邮件
另起一个PHP脚本常驻或定时跑,从队列里取任务并调用execute。这样Web请求立刻返回,邮件在后台慢慢发。
<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
while ($item = $redis->lPop('email_queue')) {
$data = unserialize($item);
$cmd = SendEmailCommand::fromArray($data);
try {
$cmd->execute();
} catch (Exception $e) {
// 失败可重新入队或记日志
$redis->rPush('email_queue', $item);
}
}
?>小结
通过命令模式封装邮件动作,并用消息队列承接,PHP项目的群发逻辑就从同步阻塞变成了异步解耦。后续还可以加上发送频率限制、失败重试和监控,让邮件系统更稳健。