记一次 swoole task 实践

本文分享了一次使用Swoole实现异步任务处理的实践经验,对比了与Redis方案的区别,详细介绍了Swoole server的配置、启动、平滑重启及监控策略,并通过压测分析了task进程数量对性能的影响。
摘要由CSDN通过智能技术生成

记一次 swoole 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进程也没死,收到的任务也仍然在按个处理

  • 测试4, 去掉sleep,onTask不处理任何逻辑空跑,lpush个20万,QPS大概12000
  • 结论
  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_BASEServer的两种运行模式 之一,这种模式下 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, 有积压发报警出来

Todo 部署运维--服务高可用

这个redis server 肯定不能部署个单点,部署多个的话,这又不是http,可以靠dns来负载均衡。。

  1. 有个可用的ip:port 清单,放哪?
  2. 单个节点服务启动和挂掉时,怎么方便的从可用清单注册和移除该服务?
  3. 调用方什么方式拿到可用清单?
  4. 服务重启了,phpredis留的坏链接怎么处理? redis->ping 一下再用
  5. 要解决高可用的问题,按上面的思路我觉得太大了。。。看来得换成http server,这样用nginx做个代理就行了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值