hyperf框架实现自定义进程

本文介绍了如何在Hyperf框架中将基于Redis、Kafka、RabbitMQ的多进程队列消费者等自定义进程与服务器分离,实现独立部署。首先,创建配置文件以设置自定义进程,然后创建基类和业务逻辑类,最后编写启动命令。这个过程包括进程初始化、信号处理和优雅重启等功能,使得进程可以单独启动和管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在实际项目中经常需要写一些长期运行的脚本,如基于 RedisKafkaRabbitMQ 实现的多进程队列消费者,多进程爬虫、定时器等等,由于hyperf的自定义进程是通过swoole\server的addProcess方式实现的,默认会跟随server一起启动,虽然可通过isEnable方法控制是否跟随服务一同启动,但是在server集群架构下,由于其耦合程度较高,在服务高可用以及横向扩容时会相当麻烦。

所以我们需要将自定义进程服务与server服务分离开了,使其可以独立部署。

第一步

添加配置(为了使用起来简单,配置内容跟hyperf的配置一致)

创建配置文件 config/autoload/task_process.php

<?php

declare (strict_types = 1);
/**
 * 自定义进程 (独立启动, 非UserProcess)
 */
return [

    \App\TaskProcess\TestProcess::class,

];

第二步

创建自定义进程基本类app/TaskProcess/BaseProcess.php

<?php

declare (strict_types = 1);

namespace App\TaskProcess;

use Hyperf\Process\AbstractProcess;
use Swoole;
use Swoole\Process;
use Swoole\Process\Manager;
use Swoole\Process\Pool;

abstract class BaseProcess {

	/**
	 * 进程数
	 * @var integer
	 */
	public $nums = 0;

	/**
	 * 进程名称
	 * @var string
	 */
	public $name = '';

	public $enableCoroutine = true;

	protected $isRunning = true;

	protected $process;

    static $signal = 0;

	function __construct() {
		//进程自动命名
		if (empty($this->name)) {
			$this->name = trim(str_replace('\\', '.', str_replace(__NAMESPACE__, '', get_called_class())), '.');
		}
	}

	final public function handle(Pool $pool, int $workerId): void {

		try {

			$this->processInit($pool->getProcess());

			$this->beforeRun();

			while (true) {
				//进程结束信号
				if (BaseProcess::$signal === SIGTERM) {
					$this->onProcessExit();
					break;
				}

				$this->run();
			}
		} catch (\Throwable $e) {
			throw $e;
		}

	}

	protected function onProcessExit() {
		$this->isRunning = false;
	}

	protected function processInit($process) {

		$this->process = $process;
        
        echo "process {$this->name} start, pid: " . getmypid().PHP_EOL;

        //注册信号处理器,实现优雅重启(等待任务执行完后或者等待超时)
		pcntl_signal(SIGTERM, function () {
			BaseProcess::$signal = SIGTERM;
			$maxWaitTime = config('server.settings.max_wait_time', 5);
			$sTime = time();
			//检查进程任务状态
			Swoole\Timer::tick(500, function () use ($sTime, $maxWaitTime) {

				$coStat = \Swoole\Coroutine::stats();
				//如果主循环结束,且其它协程任务执行完,清理定时器以退出进程
				if (!$this->isRunning && $coStat['coroutine_num'] <= 1) {
					Swoole\Timer::clearAll();
					$this->process->exit();
				}
				//等待超时,强制结束进程
				elseif (time() - $sTime >= $maxWaitTime) {

					Swoole\Timer::clearAll();
					if ($this->isRunning) {
						$this->onProcessExit();
					}
					$this->process->exit();
				}
			});
		});

	}

	public static function setProcessName(string $name) {
		swoole_set_process_name(env('APP_NAME', 'app') . '.taskProcess.' . $name);
	}

	/**
	 * 事件循环前调用
	 * @return [type] [description]
	 */
	abstract function beforeRun();

	/**
	 * 事件循环,注意这里不能使用死循环
	 * @return [type] [description]
	 */
	abstract function run();

}

创建业务逻辑类

<?php

declare (strict_types = 1);

namespace App\TaskProcess;


/**
 * test
 */
class TestProcess extends BaseProcess {

	/**
	 * 进程数
	 * @var integer
	 */
	public $nums = 1;

	public $enableCoroutine = true;



	public function beforeRun() {
		//事件循环前执行,比如一些初始化工作
	}
    
	public function run() {
        //事件循环主体    
		echo date('Y-m-d H:i:s').PHP_EOL;
        sleep(1);
	}

	

}

第三步:

自定义进程启动命令,创建app/Command/TaskProcessCommand.php

<?php

declare (strict_types = 1);

namespace App\Command;

use App\TaskProcess\BaseProcess;
use Hyperf\Command\Annotation\Command;
use Hyperf\Command\Command as HyperfCommand;
use Psr\Container\ContainerInterface;
use Swoole;
use Swoole\Process;
use Swoole\Process\Manager;
use Swoole\Process\Pool;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;

/**
 * @Command
 */
#[Command]
class TaskProcessCommand extends HyperfCommand
{
    /**
     * @var ContainerInterface
     */
    protected $container;

    protected $coroutine = false;

    protected static $process = [];

    const MANAGER_PROCESS_PID_PATH = BASE_PATH . '/runtime/taskProcess.pid';

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;

        parent::__construct('process');
    }

    public function configure()
    {
        parent::configure();
        $this->setDescription('自定义进程任务');
        $this->addOption('daemonize', 'd', InputOption::VALUE_NONE, '守护进程化');
        $this->addArgument('action', InputArgument::REQUIRED, 'start/stop/restart 启动/关闭/重启');
    }

    public function handle()
    {

        $action = $this->input->getArgument('action');

        if ($action === 'start') {
            $this->start();
        } elseif ($action === 'stop') {
            $this->stop();
        } elseif ($action === 'restart') {
            $this->restart();
        } else {
            echo "不支持的action, 请输入 -h 参数查看" . PHP_EOL;
        }

    }

    protected function restart()
    {
        $this->stop();
        $this->start();
    }

    protected function stop()
    {

        if (file_exists(self::MANAGER_PROCESS_PID_PATH)) {
            $managerPid = file_get_contents(self::MANAGER_PROCESS_PID_PATH);
            echo "stopping...\n";
            echo "kill pid $managerPid \n";
            $managerPid = intval($managerPid);
            $startTime = time();
            $timeout = config('server.settings.max_wait_time', 10);
            @Process::kill($managerPid);
            //等待主进程结束
            while (@Process::kill($managerPid, 0)) {
                //waiting process stop
                echo "waiting...\r";
                usleep(100000);
                echo "              \r";
                echo "waiting.\r";
                usleep(100000);
                echo "              \r";

                //超时 强杀所有子进程
                if ($managerPid > 0 && time() - $startTime >= $timeout) {
                    echo "wait timeout, kill -9 child process, pid: $managerPid \n";
                    echo shell_exec("ps -ef|awk '$3~/^{$managerPid}$/'") . PHP_EOL;
                    echo shell_exec("ps -ef|awk '$3~/^{$managerPid}$/ {print $2}'|xargs kill -9") . PHP_EOL;
                }
            }
            unlink(self::MANAGER_PROCESS_PID_PATH);
            echo "stopped. \n";

        } else {
            echo "找不到manager pid, path: " . self::MANAGER_PROCESS_PID_PATH;
        }

    }

    protected function start()
    {
        $processConfig = config('task_process');

        if ($processConfig) {

            echo "start now.\n";

            $daemonize = $this->input->getOption('daemonize');
            if ($daemonize) {
                //重定向标准输出到指定日志文件
                fclose(STDOUT);
                fclose(STDERR);
                $STDOUT = fopen(BASE_PATH . '/runtime/logs/taskProcess_output.log', 'ab');
                $STDERR = fopen(BASE_PATH . '/runtime/logs/taskProcess_error.log', 'ab');

                Process::daemon(true, true);
            }

            //save pid
            file_put_contents(self::MANAGER_PROCESS_PID_PATH, getmypid());

            BaseProcess::setProcessName('manager');

            $startFuncMap = [];

            foreach ($processConfig as $processClass) {
                $processObj = new $processClass;
                if (($processObj instanceof BaseProcess) && isset($processObj->nums) && $processObj->nums > 0) {
                    for ($i = 0; $i < $processObj->nums; $i++) {
                        $startFuncMap[] = [
                            [$processObj, 'handle'],
                            $processObj->enableCoroutine ?? false,
                            $i,
                        ];
                    }
                }
            }

            $pool = new Pool(count($startFuncMap), SWOOLE_IPC_UNIXSOCK, 0, false);

            $pool->on('workerStart', function (Pool $pool, int $workerId) use ($startFuncMap) {
                [$func, $enableCoroutine, $idx] = $startFuncMap[$workerId];
                if ($enableCoroutine) {
                    run(function () use ($func, $pool, $workerId, $idx) {
                        $pm = $func[0];
                        $idx += 1;
                        BaseProcess::setProcessName($pm->name . "[{$idx}/{$pm->nums}]");
                        call_user_func($func, $pool, $workerId);
                    });
                } else {
                    $func($pool, $workerId);
                }
            });

            $pool->on('Message', function (Swoole\Process\Pool $pool, string $data) {

            });

            $pool->start();


        } else {
            printf("没有可启动的自定义进程, 请在配置task_process中声明,且继承%s\n", BaseProcess::class);
        }
    }

    public function getProcess($pid = -1)
    {

        if ($pid === -1) {
            $pid = getmypid();
        }
        return static::$process[$pid] ?? null;
    }

    public function getAllProcess()
    {
        return static::$process;
    }
}

好了,一切准备就绪,通过以下命令即可启动自定义进程服务

php bin/hyperf.php process start

//在后台运行
//php bin/hyperf.php process start -d

查看进程  

ps -ef|grep taskProcess

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值