本文目录导读:

- 方案一:使用消息队列(推荐)
- 方案二:使用 Laravel Queues(框架内置)
- 方案三:使用 Gearman(分布式任务系统)
- 方案四:使用 pcntl_fork / 进程控制(简单但需谨慎)
- 方案五:使用 HTTP 请求间接实现(最简单粗糙)
- 生产环境最佳实践
在 PHP 中实现后台任务异步投递,主要有以下几种主流方案,我将从最简单到最专业依次介绍,并给出代码示例。
使用消息队列(推荐)
这是最标准的做法,尤其是生产环境,常用组件:Redis + Queue、RabbitMQ、Beanstalkd。
使用 Redis 列表实现(轻量级)
<?php
// producer.php - 生产者(投递任务)
require 'vendor/autoload.php';
use Predis\Client;
$redis = new Client([
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
]);
// 异步投递任务(发送邮件)
$taskData = [
'type' => 'send_email',
'to' => 'user@example.com',
'subject' => '欢迎注册',
'body' => '这是欢迎邮件...',
'timestamp' => time(),
];
// 推送到队列(使用序列化)
$redis->lpush('async_tasks', json_encode($taskData));
echo "任务已投递\n";
<?php
// worker.php - 消费者(执行任务)
require 'vendor/autoload.php';
use Predis\Client;
$redis = new Client([
'host' => '127.0.0.1',
'port' => 6379,
]);
// 常驻进程,循环从队列取任务
while (true) {
// BRPOP 阻塞等待任务,超时 10 秒
$task = $redis->brpop('async_tasks', 10);
if ($task) {
$data = json_decode($task[1], true);
echo "[" . date('Y-m-d H:i:s') . "] 处理任务: {$data['type']}\n";
// 根据任务类型分发处理
switch ($data['type']) {
case 'send_email':
// 实际发送邮件逻辑(可耗时较长)
sleep(2); // 模拟耗时的邮件发送
echo "邮件已发送到: {$data['to']}\n";
break;
default:
echo "未知任务类型\n";
}
}
}
启动方式:
# 启动后台消费者 php worker.php & # 或者使用 nohup 防止终端关闭退出 nohup php worker.php > worker.log 2>&1 &
使用 Laravel Queues(框架内置)
如果你使用 Laravel,直接使用它内置的队列系统。
<?php
// 1. 定义任务类
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class SendWelcomeEmail implements ShouldQueue
{
use Dispatchable, Queueable;
protected $user;
public function __construct($user)
{
$this->user = $user;
}
public function handle()
{
// 执行发送邮件逻辑
\Mail::to($this->user->email)->send(new WelcomeMail());
}
}
// 2. 投递异步任务(控制器中)
public function register(Request $request)
{
// 用户注册逻辑...
// 异步投递邮件任务
SendWelcomeEmail::dispatch($user);
return response()->json(['message' => '注册成功']);
}
启动队列处理:
# 启动队列处理器(异步执行任务) php artisan queue:work # 后台守护运行 nohup php artisan queue:work --daemon > queue.log 2>&1 &
使用 Gearman(分布式任务系统)
Gearman 适合需要复杂任务分发/负载均衡的场景。
<?php
// 1. 创建任务客户端 (producer.php)
$client = new GearmanClient();
$client->addServer('127.0.0.1', 4730);
// 异步提交任务
$client->doBackground('send_email', json_encode([
'to' => 'user@example.com',
'subject' => '你好',
'content' => '正文内容...'
]));
echo "异步任务已提交";
<?php
// 2. 创建 worker.php
$worker = new GearmanWorker();
$worker->addServer('127.0.0.1', 4730);
$worker->addFunction('send_email', 'handleEmail');
while ($worker->work()) {
echo "等待下一个任务...\n";
}
function handleEmail($job)
{
$data = json_decode($job->workload(), true);
// 处理邮件逻辑
sleep(3);
echo "邮件已发送至: {$data['to']}\n";
}
启动方式:
# 启动 gearman 服务 gearmand -d # 启动 worker php worker.php &
使用 pcntl_fork / 进程控制(简单但需谨慎)
适合简单场景,直接创建子进程。
<?php
// background.php - 异步执行函数
function async_execute($callable, ...$args) {
$pid = pcntl_fork();
if ($pid == -1) {
// fork 失败
return false;
} elseif ($pid) {
// 父进程:立即返回,不等待子进程
return $pid;
} else {
// 子进程:执行任务
call_user_func_array($callable, $args);
exit(0); // 子进程执行完退出
}
}
// 示例:异步发送邮件
$result = async_execute(function($email, $subject) {
// 耗时的邮件发送操作
sleep(3);
file_put_contents('mail_log.txt', "发送至: $email, 主题: $subject\n", FILE_APPEND);
}, 'user@example.com', '欢迎邮件');
if ($result) {
echo "任务已异步执行,PID: $result\n";
} else {
echo "无法创建子进程\n";
}
注意: 在 PHP-FPM 环境中使用fork要小心,避免连接泄漏。
使用 HTTP 请求间接实现(最简单粗糙)
使用 curl 向自身发一个带特殊标识的请求,然后立即断开连接。
<?php
// trigger.php - 异步触发
function trigger_async($url, $data) {
$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, 1);
curl_setopt($ch, CURLOPT_NOSIGNAL, true);
// 忽略返回内容
curl_setopt($ch, CURLOPT_HEADER, false);
curl_exec($ch);
curl_close($ch);
}
// 调用异步任务
trigger_async('http://your-site.com/async_handler.php', [
'type' => 'send_email',
'to' => 'user@example.com'
]);
<?php
// async_handler.php - 异步处理端点
// 设置忽略用户断开连接
ignore_user_abort(true);
set_time_limit(0);
// 立即发送响应头并关闭连接
header('Connection: close');
header('Content-Length: 0');
ob_end_flush();
flush();
// 客户端已断开,继续执行耗时任务
$type = $_POST['type'] ?? '';
switch ($type) {
case 'send_email':
sleep(3); // 模拟耗时
file_put_contents('async.log', "邮件已发送\n", FILE_APPEND);
break;
}
| 方案 | 可靠性 | 易用性 | 适用场景 | 资源消耗 |
|---|---|---|---|---|
| Redis 队列 | 高 | 中 | 中小型项目 | 低 |
| Laravel Queue | 高 | 高 | Laravel 项目 | 低 |
| Gearman | 很高 | 低 | 分布式/高并发 | 中 |
| pcntl_fork | 低 | 中 | 简单临时任务 | 低 |
| Curl 自身调用 | 低 | 高 | 快速开发/调试 | 低 |
生产环境最佳实践
- 优先选择 Redis 队列:简单、可靠、易监控。
- 失败重试机制:处理任务时捕获异常,重试或记录死信队列。
- 任务幂等性:确保同一任务重复执行不会产生副作用。
- 监控告警:监控队列积压情况,任务失败率。
- 持久化:Redis 开启 AOF/RDB,防止数据丢失。
- 优雅关闭:Worker 进程接收 SIGTERM 信号后完成当前任务再退出。
// 重试机制示例(Redis 队列)
try {
// 执行任务逻辑
processTask($data);
// 成功后删除任务(在某些队列设计中)
} catch (Exception $e) {
// 记录失败,放入重试队列
$redis->lpush('tasks_retry', json_encode([
'task' => $data,
'attempts' => ($data['attempts'] ?? 0) + 1,
'error' => $e->getMessage(),
'last_failed_at' => time(),
]));
}
选择哪种方案取决于你的项目规模、技术栈和需求。对于大多数项目,推荐 Redis + 自定义队列 或 Laravel Queue。