php Hyperf框架如何使用异步消息队列组件:hyperf/async-queue
1. 概述
Async-Queue 是 Hyperf 提供的异步消息队列组件,基于 Redis 实现,支持消息的延迟投递和失败重试。
2. 安装
composer require hyperf/async-queue
3. 配置
3.1 配置文件 config/autoload/async_queue.php
<?php
declare(strict_types=1);
return [
'default' => [
'driver' => Hyperf\AsyncQueue\Driver\RedisDriver::class,
'redis' => [
'pool' => 'default',
],
'channel' => 'async-queue',
'timeout' => 2,
'retry_seconds' => 5,
'handle_timeout' => 10,
'processes' => 1,
'concurrent' => [
'limit' => 10,
],
],
];
3.2 可选:自定义连接池
<?php
declare(strict_types=1);
return [
'default' => [
'host' => env('REDIS_HOST', 'localhost'),
'auth' => env('REDIS_AUTH', null),
'port' => (int) env('REDIS_PORT', 6379),
'db' => (int) env('REDIS_DB', 0),
'pool' => [
'min_connections' => 1,
'max_connections' => 10,
'connect_timeout' => 3.0, // 减少连接超时时间,避免长时间等待
'wait_timeout' => 2.0, // 减少等待超时时间
'heartbeat' => 30, // 添加心跳检测,每30秒检查连接
'max_idle_time' => (float) env('REDIS_MAX_IDLE_TIME', 60),
],
],
];
4. 创建 Job 类
<?php
declare(strict_types=1);
namespace App\Job;
use App\Service\MailService;
use Hyperf\AsyncQueue\Job;
use Hyperf\Context\ApplicationContext;
use Hyperf\Di\Annotation\Inject;
/**
* 邮件发送队列任务
*/
class SendEmailJob extends Job
{
/**
* @var array 任务数据
*/
protected $data;
/**
* @var MailService 邮件服务实例
*/
#[Inject(MailService::class)]
protected MailService $mailService;
/**
* @param array $data 任务数据
*/
public function __construct(array $data)
{
$this->data = $data;
}
/**
* 序列化时调用
*/
public function __sleep()
{
// 只序列化 data 属性,不序列化 mailService 属性
return ['data'];
}
/**
* 反序列化时调用
*/
public function __wakeup()
{
// 反序列化后,通过容器重新获取 mailService 实例
$container = ApplicationContext::getContainer();
$this->mailService = $container->get(MailService::class);
}
/**
* 执行任务
*/
public function handle()
{
// 记录任务开始日志
$startMessage = '邮件发送队列任务开始: ' . json_encode($this->data) . ' - 时间: ' . date('Y-m-d H:i:s') . PHP_EOL;
try {
file_put_contents(__DIR__ . '/../../runtime/logs/email_job_start.log', $startMessage, FILE_APPEND);
} catch (\Throwable $logError) {
// 忽略日志记录错误
}
try {
$to = $this->data['to'] ?? '';
$attachments = $this->data['attachments'] ?? [];
if (empty($to)) {
$emptyMessage = '邮件发送队列任务失败: 收件人邮箱为空 - 时间: ' . date('Y-m-d H:i:s') . PHP_EOL;
try {
file_put_contents(__DIR__ . '/../../runtime/logs/email_job_error.log', $emptyMessage, FILE_APPEND);
} catch (\Throwable $logError) {
// 忽略日志记录错误
}
return;
}
// 记录发送邮件开始
$sendStartMessage = '开始发送邮件: to=' . $to . ' - 时间: ' . date('Y-m-d H:i:s') . PHP_EOL;
try {
file_put_contents(__DIR__ . '/../../runtime/logs/email_job_debug.log', $sendStartMessage, FILE_APPEND);
} catch (\Throwable $logError) {
// 忽略日志记录错误
}
// 发送邮件
$result = $this->mailService->send($to, $attachments);
// 记录发送邮件结果
$sendResultMessage = '邮件发送结果: ' . ($result ? '成功' : '失败') . ' - 时间: ' . date('Y-m-d H:i:s') . PHP_EOL;
try {
file_put_contents(__DIR__ . '/../../runtime/logs/email_job_debug.log', $sendResultMessage, FILE_APPEND);
} catch (\Throwable $logError) {
// 忽略日志记录错误
}
// 记录成功日志
$successMessage = '邮件发送成功: to=' . $to . ' - 时间: ' . date('Y-m-d H:i:s') . PHP_EOL;
try {
file_put_contents(__DIR__ . '/../../runtime/logs/email_job_success.log', $successMessage, FILE_APPEND);
} catch (\Throwable $logError) {
// 忽略日志记录错误
}
} catch (\Exception $e) {
// 记录错误日志
$errorMessage = '邮件发送队列任务失败: ' . $e->getMessage() . ' - 时间: ' . date('Y-m-d H:i:s') . PHP_EOL;
try {
file_put_contents(__DIR__ . '/../../runtime/logs/email_job_error.log', $errorMessage, FILE_APPEND);
} catch (\Throwable $logError) {
// 忽略日志记录错误
}
// 记录异常详情
$exceptionMessage = '异常详情: ' . $e->getTraceAsString() . ' - 时间: ' . date('Y-m-d H:i:s') . PHP_EOL;
try {
file_put_contents(__DIR__ . '/../../runtime/logs/email_job_exception.log', $exceptionMessage, FILE_APPEND);
} catch (\Throwable $logError) {
// 忽略日志记录错误
}
} catch (\Throwable $e) {
// 捕获所有其他 Throwable
$errorMessage = '邮件发送队列任务失败: ' . $e->getMessage() . ' - 时间: ' . date('Y-m-d H:i:s') . PHP_EOL;
try {
file_put_contents(__DIR__ . '/../../runtime/logs/email_job_error.log', $errorMessage, FILE_APPEND);
} catch (\Throwable $logError) {
// 忽略日志记录错误
}
// 记录异常详情
$exceptionMessage = '异常详情: ' . $e->getTraceAsString() . ' - 时间: ' . date('Y-m-d H:i:s') . PHP_EOL;
try {
file_put_contents(__DIR__ . '/../../runtime/logs/email_job_exception.log', $exceptionMessage, FILE_APPEND);
} catch (\Throwable $logError) {
// 忽略日志记录错误
}
}
}
}
5. 投递消息
<?php
declare(strict_types=1);
namespace App\Service;
use App\Job\SendEmailJob;
use Hyperf\AsyncQueue\Driver\DriverFactory;
use Hyperf\Di\Annotation\Inject;
/**
* 邮件队列服务
*/
class EmailQueueService
{
#[Inject(DriverFactory::class)]
protected DriverFactory $driverFactory;
/**
* 将邮件发送任务加入队列
*
* @param string $to 收件人邮箱
* @param array $attachments 附件数组
* @return bool 是否成功加入队列
*/
public function push(string $to, array $attachments = []): bool
{
// 记录任务推送开始日志
$startMessage = '邮件队列推送开始: to=' . $to . ', attachments=' . json_encode($attachments) . ' - 时间: ' . date('Y-m-d H:i:s') . PHP_EOL;
try {
file_put_contents(__DIR__ . '/../../runtime/logs/email_queue.log', $startMessage, FILE_APPEND);
} catch (\Throwable $logError) {
// 忽略日志记录错误
}
try {
$driver = $this->driverFactory->get('default');
$job = new SendEmailJob([
'to' => $to,
'attachments' => $attachments
]);
// 记录创建任务
$jobMessage = '创建SendEmailJob任务: ' . json_encode(['to' => $to, 'attachments' => $attachments]) . ' - 时间: ' . date('Y-m-d H:i:s') . PHP_EOL;
file_put_contents(__DIR__ . '/../../runtime/logs/email_queue.log', $jobMessage, FILE_APPEND);
// 投递任务到队列,延迟0秒执行
$result = $driver->push($job, 0);
// 记录任务推送成功
$successMessage = '邮件队列推送成功: to=' . $to . ', result=' . var_export($result, true) . ' - 时间: ' . date('Y-m-d H:i:s') . PHP_EOL;
file_put_contents(__DIR__ . '/../../runtime/logs/email_queue.log', $successMessage, FILE_APPEND);
return true;
} catch (\Exception $e) {
// 记录详细错误日志
$errorMessage = '邮件队列推送失败: ' . $e->getMessage() . '\n堆栈信息: ' . $e->getTraceAsString() . ' - 时间: ' . date('Y-m-d H:i:s') . PHP_EOL;
try {
file_put_contents(__DIR__ . '/../../runtime/logs/email_queue_error.log', $errorMessage, FILE_APPEND);
} catch (\Throwable $logError) {
// 忽略日志记录错误
}
return false;
}
}
}
6. 失败处理(失败事件监听)
<?php
declare(strict_types=1);
namespace App\Listener;
use Hyperf\AsyncQueue\Event\FailedHandle;
use Hyperf\Event\Annotation\Listener;
use Hyperf\Event\Contract\ListenerInterface;
#[Listener]
class QueueFailedListener implements ListenerInterface
{
public function listen(): array
{
return [
FailedHandle::class,
];
}
public function process(object $event): void
{
$message = $event->getMessage();
$throwable = $event->getThrowable();
// 记录失败日志
$logger = make(\Psr\Log\LoggerInterface::class);
$logger->error('Queue job failed: ' . $throwable->getMessage(), [
'job' => get_class($message->job()),
'data' => $message->job()->params ?? null,
'attempts' => $message->getAttempts(),
]);
// 可以发送通知、存入数据库等
// $this->notifyAdmin($message, $throwable);
}
}
7. 启动消费者进程
# 启动所有队列的消费者
php bin/hyperf.php queue:listen
# 启动指定队列的消费者
php bin/hyperf.php queue:listen --queue=default
# 指定进程数
php bin/hyperf.php queue:listen --processes=2
8.守护进程配置
8.1 在 config/autoload/processes.php 中配置:
<?php
return [
Hyperf\AsyncQueue\Process\ConsumerProcess::class,
];
8.2 自定义进程数
<?php
declare(strict_types=1);
return [
// 启动 2 个消费者进程
[
'class' => Hyperf\AsyncQueue\Process\ConsumerProcess::class,
'nums' => 2,
'queue' => 'default',
],
// 另一个队列的消费者
[
'class' => Hyperf\AsyncQueue\Process\ConsumerProcess::class,
'nums' => 1,
'queue' => 'another',
],
];
9. 队列监控
public function queueStatus()
{
$driver = $this->driverFactory->get('default');
$status = $driver->info();
// 获取等待中的任务数
$waiting = $status['waiting'] ?? 0;
// 获取延迟中的任务数
$delayed = $status['delayed'] ?? 0;
// 获取失败的任务数
$failed = $status['failed'] ?? 0;
// 获取超时的任务数
$timeout = $status['timeout'] ?? 0;
return [
'waiting' => $waiting,
'delayed' => $delayed,
'failed' => $failed,
'timeout' => $timeout,
];
}
10. 注意事项
-
序列化限制:Job 中的属性必须可序列化
-
内存管理:避免在 Job 中保存大量数据
-
超时设置:根据任务复杂度合理设置超时时间
-
重试策略:合理设置最大重试次数,避免无限重试
-
监控报警:建议监控队列长度和失败任务数
-
幂等性:确保 Job 的 handle 方法是幂等的
11. 常见问题
11.1 Job 不执行
-
检查消费者进程是否启动
-
检查 Redis 连接配置
-
查看日志文件
runtime/logs/hyperf.log
11.2 内存泄漏
-
定期重启消费者进程
-
使用
queue:listen命令而不是常驻进程
11.3 性能优化
-
根据业务量调整进程数
-
使用连接池管理 Redis 连接
-
批量处理相关任务
本文来自博客园,作者:只要AD钙奶,转载请注明原文链接:https://www.cnblogs.com/carver/articles/19538557

浙公网安备 33010602011771号