workerman的redis queue队列消费实例

本文介绍了Redis作为消息队列的两种模式:生产者消费者模式(Queue)和发布订阅者模式(Topic)。在Queue模式中,消息仅被一个消费者消费,而在Topic模式下,消息可被所有订阅者消费。文中详细阐述了Queue模式的实现,包括使用brpop命令实现实时通知消费者,并给出了配置示例和代码实现,展示了在不同队列数量下的消费速度。

消息模式介绍

一般来说,消息队列有两种模式:

生产者消费者模式(Queue);

消息生产者生产消息发送到queue中,然后消息消费者从queue中取出并且消费消息。

消息被消费以后,queue中不再有存储,所以消息消费者不可能消费到已经被消费的消息。Queue支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费。

一种是发布者订阅者模式(Topic);

消息生产者(发布)将消息发布到topic中,同时有多个消息消费者(订阅)消费该消息。

和点对点方式不同,发布到topic的消息会被所有订阅者消费。

利用redis这两种场景的消息队列都能实现。

Queue 模式介绍

生产者生产消息放到队列中,多个消费者同时监听队列,谁先抢到消息谁就会从队列中取走消息,即对于每个消息最多只能被一个消费者拥有。

具体的方法就是创建一个任务队列,生产者主动lpush消息,而消费者去rpop数据。但是这样存在一个问题,就是消费者需要主动去请求数据,周期性的请求会造成资源的浪费。如果可以实现一旦有新消息加入队列就通知消费者就好了,这时借助brpop命令就可以实现这样的需求。brpop和rpop命令相似,唯一区别就是当列表中没有元素时,brpop命令会一直阻塞住连接,直到有新元素加入。

config/process.php

    // 队列消费
    'redis_consumer' => [
        'handler'     => Webman\RedisQueue\Process\Consumer::class,
        'count'       => cpu_count() * 3, // 可以设置多进程
        'constructor' => [
            // 消费者类目录
            'consumer_dir' => app_path() . '/queue/redis',
        ],
    ],
	

config/redis_queue.php

<?php

return [
    'default' => [
        'host'    => 'redis://127.0.0.1:6379',
        'options' => [
            'auth'          => env('REDIS_PASSWORD',''),            // 密码,可选参数
            'db'            => 3,                       // 数据库
            'max_attempts'  => 5,                       // 消费失败后,重试次数
            'retry_seconds' => 5,                       // 重试间隔,单位秒
        ]
    ],
];

config/redis.php

<?php
/**
 * This file is part of webman.
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the MIT-LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @author    walkor<walkor@workerman.net>
 * @copyright walkor<walkor@workerman.net>
 * @link      http://www.workerman.net/
 * @license   http://www.opensource.org/licenses/mit-license.php MIT License
 */

return [
    'default' => [
        'host'     => env('REDIS_HOST', '127.0.0.1'),
        'password' => env('REDIS_PASSWORD', ''),
        'port'     => env('REDIS_PORT', '6379'),
        'database' => 0,
    ],
];

app/queue/redis/ConverterSend.php

队列自动消费类

<?php

namespace app\queue\redis;

use Exception;
use support\Log;
use Webman\RedisQueue\Consumer;

class ConverterSend implements Consumer
{
    // 要消费的队列名
    public string $queue = 'converter_list';

    // 连接名,对应 config/redis_queue.php 里的连接`
    public string $connection = 'default';

    // 消费

    /**
     * 自动消费
     *
     * 队列生产 $contact = [
     * 'title' => $i,
     * ];
     * $a = RedisQueueService::instance()->_send($contact);
     *
     * @throws Exception
     */
    public function consume($data)
    {
        Log::info($data['title']);
    }
}

app/service/RedisQueueService.php

队列生产服务类

<?php

// *******************
// *** 消息队列生产者
// *******************

namespace app\service;

use support\Log;
use Webman\RedisQueue\Client;

class RedisQueueService
{
    public static ?RedisQueueService $_instance  = null;
    protected string                 $queue_name = 'converter_list';

    /**
     * @return RedisQueueService|mixed
     */
    public static function instance(): ?RedisQueueService
    {
        if (!static::$_instance) static::$_instance = new self();
        return static::$_instance;
    }

    /**
     * @param string $name
     * @return $this
     */
    public function setName(string $name = ''): RedisQueueService
    {
        $this->queue_name = $name;
        return $this;
    }

    /**
     * @return string
     */
    public function getName(): string
    {
        return $this->queue_name;
    }

    /**
     * 投递消息
     *
     * @param array $data
     * @param int   $delay
     * @return bool
     */
    public function _send(array $data = [], int $delay = 0): bool
    {
        $queue = $this->queue_name;
        Client::send($queue, $data, $delay);      // 投递延迟消息,消息会在 $delay 秒后处理
        return true;
    }
}

队列生产实例

$contact = [
            'title' => 1,
        ];
RedisQueueService::instance()->_send($contact);

效果消费能力速度

简单消费写入日志

Log::info($data'title');

服务器配置:

2核2G

队列数量 消费时间

  • 1W=2S
  • 10W=21S
  • 100W=1m33s
<think>嗯,用户在使用workerman/redis-queue时遇到了“There is no active transaction”的错误。我需要先理解这个错误发生的背景和可能的原因。首先,workerman/redis-queue是一个基于Redis消息队列库,通常用于处理异步任务。用户可能在处理事务时遇到了问题。 这个错误提示表明当前没有活动的事务,但代码可能尝试提交或回滚事务。常见的情况是在没有开启事务的情况下调用commit或rollback方法。我需要回忆一下redis-queue的事务机制,可能用户在使用事务相关的方法时顺序不对。 首先,检查用户是否正确地开启了一个事务。比如,在调用commit之前是否调用了beginTransaction()方法。如果没有正确开启事务,直接提交就会导致这个错误。 另外,可能用户的事务处理流程中存在提前返回或异常,导致事务没有正确开启就执行提交。例如,在beginTransaction之前有条件判断,如果条件不满足,可能跳过了begin,但后续仍然调用了commit。 还有可能用户误用了其他数据库Redis客户端的事务方法,与redis-queue的事务混淆了。需要确认用户是否在正确的对象上调用事务方法,比如是否混淆了Redis连接和队列实例的事务。 另外,可能是在处理消息时,消费者代码中的事务处理逻辑有误。比如在消息处理的回调函数中,错误地调用了事务方法,而没有正确管理事务的生命周期。 接下来,我需要考虑如何指导用户排查这个问题。首先,检查代码中是否有beginTransaction、commit、rollback的调用顺序是否正确。确保每次commit或rollback都有对应的begin。 然后,查看是否有条件分支导致beginTransaction没有被执行,但commit却被调用了。比如在某个if语句中开启了事务,但在其他分支中没有开启,却仍然执行提交。 还要检查是否有异常或错误导致事务没有被正确开启。比如在beginTransaction之后发生了异常,被捕获后继续执行commit,但此时事务并未开启。 建议用户添加日志,记录事务的开始、提交和回滚操作,确认每个commit都有对应的begin。同时,检查代码逻辑,确保事务的正确嵌套和关闭。 可能还需要查看workerman/redis-queue的文档,确认事务的正确使用方法,是否有特别需要注意的地方。例如,某些方法可能需要在特定模式下运行,或者事务的使用方式与其他库不同。 另外,如果是使用Redis的事务(multi/exec),需要确认用户是否正确地使用了相关命令,而workerman/redis-queue的事务是否与之相关。可能用户混淆了两种不同的事务概念。 最后,提供一个示例代码,展示正确的事务处理流程,帮助用户对照自己的代码进行调整。同时提醒用户处理异常情况,确保事务的正确回滚或提交。</think>在使用 `workerman/redis-queue` 时遇到 `There is no active transaction` 错误,通常是由于 **事务操作顺序不当** 导致的。以下是对问题的解析和解决方案: --- ### **问题原因** 1. **未开启事务直接提交/回滚** 当尝试调用 `commit()` 或 `rollback()` 方法时,若没有先调用 `beginTransaction()` 开启事务,会触发此错误。 2. **事务生命周期管理错误** 事务代码分支中存在提前 `return` 或异常,导致事务未正确开启却尝试提交。 3. **混淆不同组件的事务机制** 误将其他库(如数据库事务)或 Redis 原生事务(`MULTI/EXEC`)的逻辑与 `workerman/redis-queue` 的事务混合使用。 --- ### **解决方案** #### 1. **检查事务操作顺序** ```php use Workerman\RedisQueue\Client; $client = new Client('redis://127.0.0.1:6379'); // 正确流程:开启事务 → 操作 → 提交/回滚 $client->beginTransaction(); try { $client->send('queue_name', ['data' => 'example']); $client->commit(); // 提交事务 } catch (\Exception $e) { $client->rollback(); // 回滚事务 } ``` #### 2. **确保事务成对出现** ```php if ($someCondition) { $client->beginTransaction(); // 开启事务 // ... 操作 ... $client->commit(); // 提交事务 } else { // 分支中未开启事务时,避免调用 commit/rollback } ``` #### 3. **异常处理中检查事务状态** ```php $client->beginTransaction(); try { // ... 操作 ... $client->commit(); } catch (\Throwable $e) { if ($client->isInTransaction()) { // 检查是否在事务中 $client->rollback(); } throw $e; } ``` #### 4. **避免混淆 Redis 原生事务** - `workerman/redis-queue` 的事务用于确保消息原子性发送,与 Redis 的 `MULTI/EXEC` 无关。 - 若需 Redis 命令事务,需直接操作 Redis 客户端: ```php $redis = new \Redis(); $redis->connect('127.0.0.1', 6379); $redis->multi(); // ... Redis 命令 ... $redis->exec(); ``` --- ### **示例:正确使用事务** ```php use Workerman\RedisQueue\Client; $client = new Client('redis://127.0.0.1:6379'); $client->beginTransaction(); try { // 发送多个消息(事务内原子操作) $client->send('order_queue', ['task' => 'create_order']); $client->send('log_queue', ['task' => 'record_operation']); $client->commit(); // 所有消息一起提交 } catch (\Exception $e) { $client->rollback(); // 发生错误时回滚所有消息 error_log("Transaction failed: " . $e->getMessage()); } ``` --- ### **总结** - **确保事务操作成对出现**:`beginTransaction()` → `commit()`/`rollback()`。 - **分支/异常处理中检查事务状态**:避免未开启事务时提交。 - **区分组件事务机制**:`workerman/redis-queue` 的事务仅用于消息队列原子性,与 Redis 原生事务无关。 通过规范事务生命周期管理,可有效解决 `There is no active transaction` 问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值