记一次 swoole task 实践

栏目: PHP · 发布时间: 5年前

内容简介:如果你发现没有文档里manager进程,这个运行模式有关,后面再说测试了下, swoole 的 worker进程在收到请求后执行 $server->task()将任务转给task进程这个操作是非阻塞的, 后面压测会发现
  • 平时基本上都在写http+json这种api,经常遇到想把一些不重要的操作改成异步的时候
  • 之前的方案是利用redis, lpush到队列, 然后另起个 php 脚本,brpop出来操作。
  • 为了防止brpop连接闲置太久抛异常,引入的supervisor进行管理,出现异常脚本退出了supervisor会自动重启
  • 最近做一个新的独立项目,刚好看到这篇文章 在php-fpm/apache中使用task功能 ,打算实践下

开发调试跑起来

  • 首先是把代码抄过来,然后调试+加东西,完成版的代码贴出来
//$server = new Server("127.0.0.1", 9501, SWOOLE_BASE);
        $server = new Server("127.0.0.1", 9501, SWOOLE_PROCESS);

        $server->set([
            'task_worker_num'   => 2,
            'worker_num'        => 1,
        ]);

        $server->setHandler('LPUSH', function($fd, $data) use ($server) {
            $taskID = $server->task($data);

            if ($taskID === false) {
                $server->send($fd, Server::format(Server::ERROR));
            } else {
                $server->send($fd, Server::format(Server::INT, $taskID));
            }
        });
        $server->on('Start', function($serv) {
            cli_set_process_title("php_swoole_task: master");
            \Yii::info("redis server master start... pid={$serv->master_pid}", 'business');
        });

        //不回调这里,不知道为啥,进程是有的
        $server->on('ManagerStart', function($serv) {
            cli_set_process_title("php_swoole_task: manager");
            \Yii::info("redis server manager start... pid={$serv->manager_pid}", 'business');
        });

        $server->on('WorkerStart', function($serv, $worker_id) {
            $type = $serv->taskworker ? 'task' : 'worker';
            cli_set_process_title("php_swoole_task: {$type}");
            \Yii::info("redis server {$type} start ....worker_id [{$worker_id}]", 'business');
        });

        $server->on('WorkerError', function($serv, $worker_id, $worker_pid, $exit_code, $signal) {
            $type = $serv->taskworker ? 'task' : 'worker';
            $msg = "{$type} error, worker_id=[{$worker_id}], pid={$worker_pid}, exit_code=$exit_code, signal=$signal";
            \Yii::info($msg, 'business');
        });
        //task 进程处理完任务回调到这里
        $server->on('Finish', function($serv, $taskID, $data) {
            \Yii::info('redis_server task finish,id=' . $taskID . ',res=' . $data, 'business');

            $stats = $serv->stats();

            if ($stats['tasking_num'] > 10) { //tasking_num 当前正在排队的任务数
                echo "剩余任务信息:" . json_encode($serv->stats()) . "\n";
                \Yii::info('redis_server status tasking_num waring ' . json_encode($serv->stats()), 'business');
            }
        });

        // kill -9 master 进程不会触发这个回调,而且工作进程啥的都还活着
        $server->on('Shutdown', function($serv) {
            \Yii::info('redis_server shutdown....', 'business');
        });

        $server->on('Task', function ($serv, $taskID, $workerID, $data) {
            \Yii::info('redis_server receive task ' . $taskID, 'business');

            list($queue, $info) = $data;

            $info = json_decode($info, true);

            $res = true;

            switch($queue) {
                case 'present_multi_gift_order':
                    $res = LogOrderManager::addPresentSendMultiOrder($info['orderInfo'], $info['receivers']);
                    break;

                case 'present_gift_order':
                    $res = LogOrderManager::addPresentSendOrder($info['orderInfo']);
                    break;

                default:
                    echo "不认识的queue\n";
                    break;
            }

            return $res ? 'OK' : 'FAIL:' . json_encode($data);
        });

        $server->start();

复制代码
➜  ~ pstree -p 19455
-+= 00001 root /sbin/launchd
 \-+= 00758 momo /Applications/iTerm.app/Contents/MacOS/iTerm2
   \-+= 13102 momo /Applications/iTerm.app/Contents/MacOS/iTerm2 --server login -fp momo
     \-+= 13103 root login -fp momo
       \-+= 13104 momo -zsh
         \-+= 19454 root sudo php yii redis-server/start
           \-+- 19455 root php_swoole_task: master
             |--- 19460 root php_swoole_task: task
             |--- 19461 root php_swoole_task: task
             \--- 19462 root php_swoole_task: worker

复制代码

如果你发现没有文档里manager进程,这个运行模式有关,后面再说

  • 既然希望异步,那在php-fpm里lpush(提交异步任务)肯定要快啊

测试了下, swoole 的 worker进程在收到请求后执行 $server->task()将任务转给task进程这个操作是非阻塞的, 后面压测会发现

压测下 task 进程的处理能力和反应

  • 压测准备

onTask 里加个sleep(1)s 来控制每个任务的处理时间

  • redis-benchmark -h 127.0.0.1 -p 9501 -c 1 -n 20 -t lpush 进行测试
  • 测试1,启动2个task进程,lpush 1000个任务,花了8分多钟,跟推测吻合(每个任务1s,2个人干), 压测时通过 Swoole server->stats() 观察到tasking_num(排队任务数)
  • 测试2,启动20个task,lpush 1000个任务,执行时间50s
  • 测试3,还是20个进程,lpush 110万,很快就会出现 [2019-06-03 10:54:20 *10542.0] WARNING swReactor_write (ERROR 1008): socket#18 output buffer overflow , 同时 worker 进程cpu很快飚到100%

此时lpush是失败的,worker进程也没死,收到的任务也仍然在按个处理

结论
  1. 设置多少个task 进程要根据每个task处理的耗时+QPS来定。 每个任务10ms,那1s能处理100个任务,你qps是1000的话,就得启动10个task进程
  2. 如果投递容量超过处理能力,task会塞满缓存区,导致worker进程发生阻塞。worker进程将无法接收新的请求;但是已经转给task进程的任务会继续执行

部署运维---平滑重启

这种常驻内存的服务不想nginx+php-fpm,需要我们自己写脚本来搞定这个事情

  • 需求我总结了下
  1. 代码部署完能自动生效,立刻还是延迟点无所谓
  2. 同时不影响正在处理的任务
  3. 调用方没有感知
  1. kill -9 master_pid 只是干掉master,其他还活着
  2. kill -15 master_pid 干掉所有
  3. kill -USR1 平滑重启所有worker进程
  4. kill -USR2 平滑重启所有task进程
[Unit]
Description=Swoole Task Server
After=network.target
After=syslog.target

[Service]
Type=simple
LimitNOFILE=65535
ExecStart=/usr/bin/php /home/deploy/api-mj/yii redis-server/start
ExecReload=/bin/kill -USR2 $MAINPID
Restart=always

[Install]
WantedBy=multi-user.target

复制代码
  • 测试结果
  1. task进程不重启, 新部署的代码是不会生效的
  2. systemctl restart swoole_task.service 所有进程都重启,积压的task会丢弃
  3. systemctl reload swoole_task.service 也就是 kill -USR2 {master_pid} , 会启动新task进程,旧task进程会继续处理积压的任务,处理完后退出

坑 SWOOLE_BASE 模式

  • $server = new Server("127.0.0.1", 9501, SWOOLE_BASE); 这句. SWOOLE_BASE 是Server的两种运行模式 之一,这种模式下 kill -USR1 或者 kill -USR2 都只能重启worker进程,不会重启task进程,也就做不到平滑重启(因为无法让新代码生效)

  • SWOOLE_BASE 模式下运行的结果跟文档说的也有点不一样

  1. 文档说BASE模式没有master进程,我发现是有的
  2. 文档说manager进程可选,我测试的结果是不管怎么着都没有manager进程
  • 测试代码
//$server = new Server("127.0.0.1", 9501, SWOOLE_BASE);
        $server = new Server("127.0.0.1", 9501, SWOOLE_PROCESS);

        $server->set([
            'task_worker_num'   => 2,
            'worker_num'        => 1,
        ]);

复制代码
  • SWOOLE_PROCESS 模式下, master(1)+manager(1)+worker(1)+task(2) 共5个
➜  ~ pstree -p 19319
-+= 00001 root /sbin/launchd
 \-+= 00758 momo /Applications/iTerm.app/Contents/MacOS/iTerm2
   \-+= 13102 momo /Applications/iTerm.app/Contents/MacOS/iTerm2 --server login -fp momo
     \-+= 13103 root login -fp momo
       \-+= 13104 momo -zsh
         \-+= 19318 root sudo php yii redis-server/start
           \-+- 19319 root php_swoole_task: master
             \-+- 19320 root php_swoole_task: manager
               |--- 19321 root php_swoole_task: task
               |--- 19322 root php_swoole_task: task
               \--- 19323 root php_swoole_task: worker

复制代码
  • SWOOLE_BASE 模式下 master(1)++worker(1)+task(2) 共4个
➜  ~ pstree -p 19455
-+= 00001 root /sbin/launchd
 \-+= 00758 momo /Applications/iTerm.app/Contents/MacOS/iTerm2
   \-+= 13102 momo /Applications/iTerm.app/Contents/MacOS/iTerm2 --server login -fp momo
     \-+= 13103 root login -fp momo
       \-+= 13104 momo -zsh
         \-+= 19454 root sudo php yii redis-server/start
           \-+- 19455 root php_swoole_task: master
             |--- 19460 root php_swoole_task: task
             |--- 19461 root php_swoole_task: task
             \--- 19462 root php_swoole_task: worker

复制代码

Todo 部署运维--监控

  • 服务挂掉了要报警 机器监控进程是否活者 + 定时ping下看是否活着
  • task finish回调里,看下剩余的tasking_num, 有积压发报警出来

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

算法之美

算法之美

左飞 / 电子工业出版社 / 2016-3 / 79.00元

《算法之美——隐匿在数据结构背后的原理(C++版)》围绕算法与数据结构这个话题,循序渐进、深入浅出地介绍了现代计算机技术中常用的40 余个经典算法,以及回溯法、分治法、贪婪法和动态规划等算法设计思想。在此过程中,《算法之美——隐匿在数据结构背后的原理(C++版)》也系统地讲解了链表(包括单向链表、单向循环链表和双向循环链表)、栈、队列(包括普通队列和优先级队列)、树(包括二叉树、哈夫曼树、堆、红黑......一起来看看 《算法之美》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换