PHPAPI邮件发送怎么异步

wen PHP项目 7

本文目录导读:

PHPAPI邮件发送怎么异步

  1. 使用消息队列(推荐方案)
  2. 使用 Redis 队列
  3. 使用 PHP 的 pcntl_fork (Linux/Mac)
  4. 使用第三方服务(推荐生产环境)
  5. 使用 PHP 内置的 curl_multi
  6. 最佳实践建议
  7. 完整的异步邮件处理框架

在 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);
    }
}

最佳实践建议

  1. 生产环境推荐使用消息队列(RabbitMQ、Redis、Amazon SQS)
  2. 避免在 Web 请求中直接发送邮件
  3. 添加重试机制和错误处理
  4. 监控邮件发送状态和队列长度
  5. 考虑使用专业的邮件服务(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 进程就足够了。

抱歉,评论功能暂时关闭!