本文目录导读:

- 使用消息队列(推荐方案)
- 使用 Redis 队列
- 使用 PHP 的 pcntl_fork (Linux/Mac)
- 使用第三方服务(推荐生产环境)
- 使用 PHP 内置的 curl_multi
- 最佳实践建议
- 完整的异步邮件处理框架
在 PHP 中实现异步邮件发送通常有几种方案,由于 PHP 默认是同步阻塞的,需要借助额外工具或技术来实现真正的异步。
使用消息队列(推荐方案)
RabbitMQ 示例
// 生产者:发送邮件到队列
<?php
require_once 'vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
class EmailProducer {
private $connection;
private $channel;
public function __construct() {
$this->connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$this->channel = $this->connection->channel();
$this->channel->queue_declare('email_queue', false, true, false, false);
}
public function sendToQueue($to, $subject, $body) {
$data = json_encode([
'to' => $to,
'subject' => $subject,
'body' => $body,
'timestamp' => time()
]);
$msg = new AMQPMessage($data, [
'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT
]);
$this->channel->basic_publish($msg, '', 'email_queue');
}
public function __destruct() {
$this->channel->close();
$this->connection->close();
}
}
// 使用
$producer = new EmailProducer();
$producer->sendToQueue('user@example.com', 'Hello', 'Email body');
消费者:处理队列中的邮件
// consumer.php - 需要作为独立进程运行
<?php
require_once 'vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
class EmailConsumer {
private $connection;
private $channel;
public function __construct() {
$this->connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$this->channel = $this->connection->channel();
$this->channel->queue_declare('email_queue', false, true, false, false);
// 限制每次处理1个任务
$this->channel->basic_qos(null, 1, null);
}
public function process() {
$callback = function (AMQPMessage $msg) {
$data = json_decode($msg->body, true);
echo "Processing email to: {$data['to']}\n";
// 发送邮件
$this->sendEmail($data['to'], $data['subject'], $data['body']);
// 确认消息已处理
$msg->ack();
};
$this->channel->basic_consume('email_queue', '', false, false, false, false, $callback);
while ($this->channel->is_consuming()) {
$this->channel->wait();
}
}
private function sendEmail($to, $subject, $body) {
// 实际的邮件发送逻辑
sleep(2); // 模拟发送延迟
echo "Email sent to: $to\n";
}
}
$consumer = new EmailConsumer();
$consumer->process();
使用 Redis 队列
<?php
require_once 'vendor/autoload.php';
use Predis\Client;
class RedisEmailQueue {
private $redis;
private $queueName = 'email_queue';
public function __construct() {
$this->redis = new Client([
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
]);
}
// 入队
public function enqueue($emailData) {
$this->redis->rpush($this->queueName, json_encode($emailData));
return true;
}
// 出队并处理
public function dequeue() {
$data = $this->redis->lpop($this->queueName);
if ($data) {
$emailData = json_decode($data, true);
$this->processEmail($emailData);
}
}
private function processEmail($data) {
// 实际发送邮件
mail($data['to'], $data['subject'], $data['body']);
}
}
// 使用
$queue = new RedisEmailQueue();
$queue->enqueue([
'to' => 'user@example.com',
'subject' => 'Test',
'body' => 'Hello World'
]);
使用 PHP 的 pcntl_fork (Linux/Mac)
<?php
class AsyncEmailSender {
public static function send($to, $subject, $body) {
$pid = pcntl_fork();
if ($pid == -1) {
// fork失败
return false;
} elseif ($pid) {
// 父进程:立即返回
return true;
} else {
// 子进程:执行邮件发送
self::sendEmail($to, $subject, $body);
exit(0);
}
}
private static function sendEmail($to, $subject, $body) {
// 实际的邮件发送逻辑
mail($to, $subject, $body);
}
}
// 使用
AsyncEmailSender::send('user@example.com', 'Subject', 'Body');
使用第三方服务(推荐生产环境)
SendGrid API
<?php
require_once 'vendor/autoload.php';
use SendGrid\Mail\Mail;
class AsyncEmailService {
public function sendEmailAsync($to, $subject, $content) {
// 使用 HTTP 客户端发送请求但不等待响应
$client = new GuzzleHttp\Client([
'timeout' => 0.1 // 极短超时,触发异步
]);
$promise = $client->postAsync('https://api.sendgrid.com/v3/mail/send', [
'headers' => [
'Authorization' => 'Bearer YOUR_API_KEY',
'Content-Type' => 'application/json',
],
'json' => [
'personalizations' => [
['to' => [['email' => $to]]]
],
'from' => ['email' => 'from@example.com'],
'subject' => $subject,
'content' => [
['type' => 'text/plain', 'value' => $content]
]
]
]);
// 不等待结果
$promise->then(function() {
// 可选:记录成功日志
}, function() {
// 可选:记录失败日志
});
return true;
}
}
使用 PHP 内置的 curl_multi
<?php
class CurlAsyncMailer {
public static function sendAsync($url, $data) {
$mh = curl_multi_init();
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 100); // 快速超时
curl_multi_add_handle($mh, $ch);
do {
curl_multi_exec($mh, $running);
} while ($running > 0);
curl_multi_remove_handle($mh, $ch);
curl_multi_close($mh);
}
}
最佳实践建议
- 生产环境推荐使用消息队列(RabbitMQ、Redis、Amazon SQS)
- 避免在 Web 请求中直接发送邮件
- 添加重试机制和错误处理
- 监控邮件发送状态和队列长度
- 考虑使用专业的邮件服务(SendGrid、Mailgun、Amazon SES)
完整的异步邮件处理框架
// config.php
return [
'queue' => [
'driver' => 'redis', // 或 rabbitmq
'host' => 'localhost',
'port' => 6379,
],
'mailer' => [
'driver' => 'smtp',
'host' => 'smtp.gmail.com',
'port' => 587,
'username' => 'your@email.com',
'password' => 'your-password',
]
];
// EmailQueue.php
class EmailQueue {
private $queue;
private $config;
public function __construct($config) {
$this->config = $config;
$this->initializeQueue();
}
public function push(Email $email) {
$this->queue->enqueue(serialize($email));
}
public function process() {
while (!$this->queue->isEmpty()) {
$email = unserialize($this->queue->dequeue());
$this->sendEmail($email);
}
}
private function sendEmail(Email $email) {
try {
// 实际的邮件发送逻辑
mail($email->to, $email->subject, $email->body);
$this->logSuccess($email);
} catch (Exception $e) {
$this->logError($email, $e);
// 重新入队或移到失败队列
}
}
}
选择方案时要考虑你的具体需求:并发量、可靠性要求、维护成本等,对于大多数中小型应用,使用 Redis 队列配合后台 worker 进程就足够了。