PHP 后台任务异步投递

wen PHP项目 2

本文目录导读:

PHP 后台任务异步投递

  1. 方案一:使用消息队列(推荐)
  2. 方案二:使用 Laravel Queues(框架内置)
  3. 方案三:使用 Gearman(分布式任务系统)
  4. 方案四:使用 pcntl_fork / 进程控制(简单但需谨慎)
  5. 方案五:使用 HTTP 请求间接实现(最简单粗糙)
  6. 生产环境最佳实践

在 PHP 中实现后台任务异步投递,主要有以下几种主流方案,我将从最简单最专业依次介绍,并给出代码示例。

使用消息队列(推荐)

这是最标准的做法,尤其是生产环境,常用组件:Redis + QueueRabbitMQBeanstalkd

使用 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 自身调用 快速开发/调试

生产环境最佳实践

  1. 优先选择 Redis 队列:简单、可靠、易监控。
  2. 失败重试机制:处理任务时捕获异常,重试或记录死信队列。
  3. 任务幂等性:确保同一任务重复执行不会产生副作用。
  4. 监控告警:监控队列积压情况,任务失败率。
  5. 持久化:Redis 开启 AOF/RDB,防止数据丢失。
  6. 优雅关闭: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

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