記一次 swoole task 實踐

記一次 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

  • 既然但願異步,那在php-fpm裏lpush(提交異步任務)確定要快啊

測試了下, swoole 的 worker進程在收到請求後執行 $server->task()將任務轉給task進程這個操做是非阻塞的, 後面壓測會發現html

壓測下 task 進程的處理能力和反應

  • 壓測準備

onTask 里加個sleep(1)s 來控制每一個任務的處理時間nginx

  • 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進程也沒死,收到的任務也仍然在按個處理redis

  • 測試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,須要咱們本身寫腳原本搞定這個事情apache

  • 需求我總結了下
  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進程,也就作不到平滑重啓(由於沒法讓新代碼生效)json

  • SWOOLE_BASE 模式下運行的結果跟文檔說的也有點不同api

  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作個代理就好了
相關文章
相關標籤/搜索