求助关于webman中redis-queue平滑重启的问题

Fool.

问题描述

概述:
webman + console + redis-queue 尝试平滑重启或停止,队列监听的 worker 进程无法正常终止。
直接stop 会导致执行中的队列任务执行中断,可能会引发业务问题。

环境:

macOS 10.15  x86_64
php 7.3.33(NTS)
redis_version:6.0.8

composer 相关库及版本
"name": "workerman/webman-framework","version": "v1.4.10"
"name": "webman/console","version": "v1.2.18"
"name": "webman/redis-queue", "version": "v1.2.4"
"name": "workerman/redis-queue",  "version": "v1.0.10",

测试相关源码 为减少篇幅 代码换行有删减

// 1. 参考 业务初始化文档 补充了工作进程信息打印
// see: https://www.workerman.net/doc/webman/others/bootstrap.html
<?php
namespace app\bootstrap;
use Webman\Bootstrap;
class MemReport implements Bootstrap {
    public static function start($worker) {
        $is_console = !$worker;
        if ($is_console) {  return; }
        if ($worker->name == 'monitor') {  return;   }

        \Workerman\Timer::add(10, function () use($worker) {
            echo $worker->name .':'. $worker->id .' memUsage:'. formatBytes(memory_get_usage()) . PHP_EOL;
        });
//...

//2.参考 redis-queue 文档,添加了队列任务 及 消费脚本
// see:  https://www.workerman.net/plugin/12

//2.1 任务添加脚本
<?php
defined('BASE_PATH') or define('BASE_PATH', dirname(__DIR__));
require_once BASE_PATH . '/vendor/autoload.php';
require_once BASE_PATH . '/support/bootstrap.php';
use Webman\RedisQueue\Redis;
// 队列名
$queue = 'send-mail';
// 数据,可以直接传数组,无需序列化
$data = ['to' => 'tom@gmail.com', 'content' => 'hello'];
/** @see \Webman\RedisQueue\RedisConnection::send() */
// 投递消息
$t = Redis::send($queue, $data);
var_dump($t);
// 投递延时消息
$t = Redis::send($queue, $data, 5);
var_dump($t);

//2.2 消费脚本
<?php
namespace app\queue\redis;
use support\Log;
use Webman\RedisQueue\Consumer;
use Workerman\Worker;
class TestConsumer implements Consumer {
    // 要消费的队列名
    public $queue = 'send-mail';
    // 连接名,对应 plugin/webman/redis-queue/redis.php 里的连接`
    public $connection = 'default';
    // 消费
    public function consume($data)  {
        // 无需反序列化
        var_export($data); // 输出 ['to' => 'tom@gmail.com', 'content' => 'hello']
        Log::info('consume start...');
        sleep(10);
        $total = 0;
        for ($i = 0; $i < 1000; $i++) {
            $total += $i;
            usleep(5000);
        }
        Log::info('total:'.$total);
        //实际会有一些db相关操作
        sleep(3);
        var_export(['update consumer 111']);
        Log::info('consume done.');
    }
}

测试流程概述

调整部分配置,方便验证
        config/server.php   count web服务进程数设置为2   stop_timeout 终止超时设置为 3
        config/plugin/webman/redis-queue/process.php  consumer count  消费进程数设置为 1
执行 任务投递脚本  向redis队列中添加一些待消费的任务消息
启动webman  `php webman start`
     观察控制台的打印信息,webman 及 队列监听进程均正常启动
     队列开始消费

尝试执行 `php webman stop -g`  或 `php webman restart -g`
    Workerman[webman] stop
    Workerman[webman] is gracefully stopping ...
    //...无法正常完成

描述
    webman 服务工作进程 正常终止
    队列worker进程 无法终止
    服务启动对应的控制台持续输出 `plugin.webman.redis-queue.consumer:0 memUsage:6.55MB`

以上,还请大佬们帮忙解惑

为此你搜索到了哪些方案及不适用的原因

  • webman/redis-queue 扩展说明文档,未提及 平滑重启 或 停止监听
  • redis-queue 订阅是否可以设置开关 https://www.workerman.net/q/7649 建议补充参数或变量
    webman/redis-queue Webman\RedisQueue\Process\Consumer 是在onWorkerStart 时启动的订阅
    设置变量 不适用于在监听过程中 取消监听
  • 关于workerman 停止失败的faq https://www.workerman.net/doc/workerman/faq/stop-fail.html ,文档中提到的几种可能性也检查验证过了,可以排除
  • 其他,暂未搜索到 类似文档
1338 2 1
2个回答

walkor 打赏

stop -g 或者 restart -g 原理是当进程内部所有连接对象都销毁时,执行退出。因为队列里有一个到redis的长连接,不会销毁,所以无法停止。也就是说有长连接类的应用不支持stop -g 或者 restart -g,包括队列

  • Fool. 2023-01-05

    嗯, 那请教下 关于队列监听,在保障不中断消费中的任务 的前提下,要终止监听,有什么好的处理方案或者建议么。
    翻了下 stop/restart 以及 consumer 的相关源码,目前还没找到什么调整方案

  • walkor 2023-01-05

    stop_timeout 设置的值大于最大消费时间就好了

  • Fool. 2023-01-05

    重试了一下, stop_timeout 设置为30 > 20 (消费时间),
    基于 stop / restart -g 无法使用,只能直接 stop或restart
    测试验证 stop / restart 还是会直接终止 消费中的任务,似乎也不太适用安全终止监听的需求场景
    我再看看有没有什么办法吧, 也可能担心是多余的 (安全终止监听 = 伪需求)

    感谢大佬 :)

evilk

我们的config/server.php/stop_timeout设置的是30s,大于消费的最大时间
我们测试过,直接使用php start.php restart -d重启webman服务,并不会直接中断正在消费的任务
会等到任务消费完成后,才会安全重启服务(进程)

  • Fool. 2023-01-05

    试了下 -d 后台运行的模式 还是不行,消费者 处理逻辑如下

            Log::info('consume start...');
            echo 'sleep...';
            sleep(10);
            Log::info('query db..');
            $list = Db::table('admin_log')
                ->first();
            Log::info('sleep again...');
            sleep(10);
            echo 'sleep done.';
            Log::info('queryData:'.json_encode($list));
            Log::info('consume done.');

    日志文件里面 只有 consume start...
    restart 和 stop 都是, stop_timeout 设置 时间30 还是 60 没差异
    不清楚 是不是跟你的版本不一样 导致的, 也不知道是不是其他原因

年代过于久远,无法发表回答
×
🔝