标题:【Swoole系列6.5】Hyperf中的其它事项

文章目录
    分类:PHP 标签:Swoole

    Hyperf中的其它事项

    关于 Hyperf 其它的内容我们就不多说了,毕竟框架这东西用得多了自然也就熟悉了。最重要的是——我的水平还不足以去深入地分析这个框架!


    好吧,其它的功能大家可以去官方文档详细了解,毕竟国人自己做的框架,文档和服务支持还是非常方便的。今天,我们就来再简单讲讲其它的一些配置。

    Nginx部署

    第一个就是 Nginx 部署问题,Nginx 做为现在顶流级别的应用服务器,可以非常方便地实现 HTTP 服务、绑定域名、负载均衡等功能。在传统的 PHP-FPM 时代,我们只需要指定 FastCGI ,也就是那个 9000 端口或者 unixSocket 就可以了。其实也可以看出,fastcgi_pass 这个词本身就是通过什么什么来执行的意思。fastcgi_pass 就是通过 fastcgi 来执行 PHP-FPM 程序从而实现应用程序的代理。


    Swoole 本身启动的就是一个服务应用,这种情况最方便的当然就是来一个反向代理搞定啦。

    # 至少需要一个 Hyperf 节点,多个配置多行
    upstream hyperf {
        # Hyperf HTTP Server 的 IP 及 端口
        server 127.0.0.1:9501;
        server 127.0.0.1:9502;
    }
    
    server {
        # 监听端口
        listen 80; 
        # 绑定的域名,填写您的域名
        server_name www.testswoole.com;
    
        location / {
            # 将客户端的 Host 和 IP 信息一并转发到对应节点  
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            
            # 转发Cookie,设置 SameSite
            proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
            
            # 执行代理访问真实服务器
            proxy_pass http://hyperf;
        }
    }

    这是官网的例子,配置都比较简单,也没什么多说的,不过需要注意的是,一定要配置 IP 的转发,要不你在程序中获得的 IP 就是 127.0.0.1 那个了。这个只要配置过反向代理的应该都清楚,也不多做解释了哦。

    Supervisor部署

    除了 Nginx 之外,还推荐一个工具,那就是 Supervisor 。它是一个进程管理工具,本身 PHP-FPM 就可以自动管理进程,同时我们之前也讲过在 Swoole 中如何管理进程。但是,Supervisor 相对来说更加强大一些,可以很方便地启动、监听和重启一个或多个进程。当一个进程意外被 kill 时,它就会自动再把它重启或者拉起新的进程,从而达到进程自动恢复的目的。

    # 安装 epel 源,如果此前安装过,此步骤跳过
    yum install -y epel-release
    yum install -y supervisor  

    CentOS 的话直接运行上面两个命令行就可以安装 Supervisor 了。如果你有使用过 宝塔面板 之类的面板工具的话,那就更方便了,里面可以直接选择安装 Supervisor 并且能够图形界面化的管理。


    如果你不爱使用面板类的工具的话,那就还是跟着我一起向下配置吧。先来创建一个配置文件。

    vim /etc/supervisord.d/hyperf.ini

    在这个 .ini 文件中添加下面的内容。

    # 新建一个应用并设置一个名称,这里设置为 hyperf
    [program:hyperf]
    # 设置命令在指定的目录内执行
    directory=/home/www/6.框架/hyperf-skeleton/
    # 这里为您要管理的项目的启动命令
    command=php ./bin/hyperf.php start
    # 以哪个用户来运行该进程
    user=root
    # supervisor 启动时自动该应用
    autostart=true
    # 进程退出后自动重启进程
    autorestart=true
    # 进程持续运行多久才认为是启动成功
    startsecs=1
    # 重试次数
    startretries=3
    # stderr 日志输出位置
    stderr_logfile=/home/www/6.框架/hyperf-skeleton/runtime/stderr.log
    # stdout 日志输出位置
    stdout_logfile=/home/www/6.框架/hyperf-skeleton/runtime/stdout.log

    注意上面路径相关的内容,要修改成你的项目路径哦。然后就可以启动 Supervisor 了。

    supervisord -c /etc/supervisord.d/supervisord.conf

    这一行的意思是以配置文件启动 Supervisor 主程序,同时之前我们配置过的 .ini 文件中的程序都会运行起来。

    [root@localhost supervisord.d]# ps -ef | grep skeleton
    root     32162 32161  0 07:25 ?        00:00:00 skeleton.Master
    root     32170 32162  0 07:25 ?        00:00:00 skeleton.Manager
    root     32172 32170  0 07:25 ?        00:00:00 skeleton.TaskWorker.1
    root     32173 32170  0 07:25 ?        00:00:00 skeleton.TaskWorker.2
    root     32174 32170  0 07:25 ?        00:00:00 skeleton.TaskWorker.3
    root     32175 32170  0 07:25 ?        00:00:00 skeleton.TaskWorker.4
    root     32176 32170  0 07:25 ?        00:00:00 skeleton.TaskWorker.5
    root     32177 32170  0 07:25 ?        00:00:00 skeleton.TaskWorker.6
    root     32178 32170  0 07:25 ?        00:00:00 skeleton.TaskWorker.7
    root     32179 32170  0 07:25 ?        00:00:00 skeleton.TaskWorker.8
    root     32180 32170  0 07:25 ?        00:00:00 skeleton.Worker.0

    不对呀,我们程序名字怎么是 skeleton ,而且 gerp php 也看不到内容。没错,同时也不要慌张,进入项目目录,修改一下 .env 中 APP_NAME ,我就修改成了 MNLZ 。接着重启一下 Supervisor 应用服务。

    [root@localhost hyperf-skeleton]# supervisorctl restart hyperf
    hyperf: stopped
    hyperf: started
    [root@localhost hyperf-skeleton]# ps -ef | grep MNLZ
    root     32213 32161  1 07:29 ?        00:00:00 MNLZ.Master
    root     32221 32213  0 07:29 ?        00:00:00 MNLZ.Manager
    root     32223 32221  0 07:29 ?        00:00:00 MNLZ.TaskWorker.1
    root     32224 32221  0 07:29 ?        00:00:00 MNLZ.TaskWorker.2
    root     32225 32221  0 07:29 ?        00:00:00 MNLZ.TaskWorker.3
    root     32226 32221  0 07:29 ?        00:00:00 MNLZ.TaskWorker.4
    root     32227 32221  0 07:29 ?        00:00:00 MNLZ.TaskWorker.5
    root     32228 32221  0 07:29 ?        00:00:00 MNLZ.TaskWorker.6
    root     32229 32221  0 07:29 ?        00:00:00 MNLZ.TaskWorker.7
    root     32230 32221  0 07:29 ?        00:00:00 MNLZ.TaskWorker.8
    root     32231 32221  0 07:29 ?        00:00:00 MNLZ.Worker.0

    现在你可以试试 kill 一下,不管是 TaskWorker 还是 Master ,kill 之后都会重新拉起新的进程。但是注意,如果 Manager 出现问题了,那可就拉不起任何子进程了。毕竟,Manager 是整个 Swoole 中的管理进程。还记得我们之前讲过的进程模式相关的内容吗?如果不记得了,可以回去看看哦 【Swoole系列3.2】Swoole异步进程服务系统https://mp.weixin.qq.com/s/raIfojXP7u1CPD9cpj7g5A


    最后,为什么要使用 Supervisor 呢?之前其实我们也讲过,Swoole 中的一个异常或者错误就会导致进程被关闭,为了保证有足够的子进程来处理请求,Supervisor 就是非常好的选择,特别是预防 Master 进程的突然中断。下面的一些命令行命令大家也可以了解一下。

    # 启动 hyperf 应用
    supervisorctl start hyperf
    # 重启 hyperf 应用
    supervisorctl restart hyperf
    # 停止 hyperf 应用
    supervisorctl stop hyperf  
    # 查看所有被管理项目运行状态
    supervisorctl status
    # 重新加载配置文件
    supervisorctl update
    # 重新启动所有程序
    supervisorctl reload

    Hyperf 核心生命周期

    Hyperf 的生命周期其实分两个部分,在官方文档上也就两段说明。我带着大家去翻源码再看一下。


    在 Hyperf 中,它没有像 Laravel 一样的 public/index.php 这样的请求入口文件。因为它是需要自己启动服务的,所以它的全部入口都是 bin/hyperf.php 这个命令行文件。

    框架生命周期

    当我们执行 php bin/hyperf.php start 的时候,实际上是 vendor/hyperf/server/src/Command/StartServer.php 这个文件中的命令被执行了。这一系列操作我们之前在学习 Laravel 命令行时也接触过。

    protected function execute(InputInterface $input, OutputInterface $output)
        {
            $this->checkEnvironment($output);
    
            $serverFactory = $this->container->get(ServerFactory::class)
                ->setEventDispatcher($this->container->get(EventDispatcherInterface::class))
                ->setLogger($this->container->get(StdoutLoggerInterface::class));
    
            $serverConfig = $this->container->get(ConfigInterface::class)->get('server', []);
            if (! $serverConfig) {
                throw new InvalidArgumentException('At least one server should be defined.');
            }
    
            $serverFactory->configure($serverConfig);
    
            Coroutine::set(['hook_flags' => swoole_hook_flags()]);
    
            $serverFactory->start();
    
            return 0;
        }

    在这个对象的 execute() 中,我们就可以非常清楚地看到读取到了配置文件 config/autoload/server.php 中的内容,然后交给一个 $serverFactory 对象去启动。


    $serverFactory 的 configure() 方法会根据配置文件信息,返回实际的原生 Swoole 服务对象。

    public function configure(array $config)
    {
        $this->config = new ServerConfig($config);
    
        $this->getServer()->init($this->config);
    }
    public function getServer(): ServerInterface
    {
        if (! $this->server instanceof ServerInterface) {
            $serverName = $this->config->getType();
            $this->server = new $serverName(
                $this->container,
                $this->getLogger(),
                $this->getEventDispatcher()
            );
        }
    
        return $this->server;
    }

    ServerConfig 对象会根据我们的配置文件生成一个格式化的配置对象。然后在下面的 getServer() 方法中,根据 ServerConfig 对象的 getType() 返回值获得一个指定的 Server 对象。注意,这个 getType() 返回的不是我们配置文件中的那个 type 属性哦。

    class ServerConfig implements Arrayable
    {
        // ......
        public function __construct(array $config = [])
        {
            // ......
    
            $this->setType($config['type'] ?? Server::class)
                ->setMode($config['mode'] ?? 0)
                ->setServers($servers)
                ->setProcesses($config['processes'] ?? [])
                ->setSettings($config['settings'] ?? [])
                ->setCallbacks($config['callbacks'] ?? []);
        }
        // ......
    }

    注意看这里的 setType() 它要拿的是整个 server.php 配置文件中最外层的 type 属性,我们并没有定义这个值,所以返回的就是 vendor/hyperf/server/src/Server.php 这个对象。接下来我们顺着这个对象的 init() 方法向下摸,在 initServers() 方法中发现了一个 makeServer() 方法的调用,感觉离胜利不远了哦。

    protected function makeServer(int $type, string $host, int $port, int $mode, int $sockType)
    {
        switch ($type) {
            case ServerInterface::SERVER_HTTP:
                return new SwooleHttpServer($host, $port, $mode, $sockType);
            case ServerInterface::SERVER_WEBSOCKET:
                return new SwooleWebSocketServer($host, $port, $mode, $sockType);
            case ServerInterface::SERVER_BASE:
                return new SwooleServer($host, $port, $mode, $sockType);
        }
    
        throw new RuntimeException('Server type is invalid.');
    }

    我擦,不对呀,SwooleHttpServer 又是什么鬼,再进去看看,我们就选第一个 SwooleHttpServer 。点过去之后总算真象大白了,SwooleHttpServer 是一个命名空间别名,真实的就是 Swoole 下面的各种服务器。

    // vendor/swoole/ide-helper/src/swoole/Swoole/Http/Server.php
    class Server extends \Swoole\Server
    {
    }

    这一条线大家摸清楚了吧,这就是我们说的第一个生命周期,也就是整个框架运行起 Swoole 应用服务的生命周期。

    请求与协程生命周期

    另一个就是请求与协程的生命周期,这里我就搬官方原话了,其实这部分内容我们之前讲过。


    Swoole 在处理每个连接时,会默认创建一个协程去处理,主要体现在 onRequest、onReceive、onConnect 事件,所以可以理解为每个请求都是一个协程,由于创建协程也是个常规操作,所以一个请求协程里面可能会包含很多个协程,同一个进程内协程之间是内存共享的,但调度顺序是非顺序的,且协程间本质上是相互独立的没有父子关系,所以对每个协程的状态处理都需要通过 协程上下文 来管理。

    总结

    到这里,我们整个 Hyperf 框架的学习就结束了,同时,整个 Swoole 系列也就告一段落了。这里先不煽情了,毕竟后面还有一篇大总结,大家有收获吗?不管怎么样,一步一步跟着我走下来,相信多少都会有一点感悟和成长。更重要的,如果有机会,不如尝试在实战中运用一下,这才是真正成长的最佳机会。


    测试代码:


    https://github.com/zhangyue0503/swoole/tree/main/6.%E6%A1%86%E6%9E%B6/hyperf-skeleton


    参考文档:


    https://hyperf.wiki/2.2/#/zh-cn/tutorial/nginx


    https://hyperf.wiki/2.2/#/zh-cn/tutorial/supervisor


    https://hyperf.wiki/2.2/#/zh-cn/lifecycle

    视频链接

    微信文章地址:https://mp.weixin.qq.com/s/K6YMPqnyBzWxWM52Tvjx3A

    微信视频地址:https://mp.weixin.qq.com/s/-hS3Ck7OXLprwVEvwfbw7g

    B站视频地址:https://www.bilibili.com/video/BV1sW4y147dD

    搜索
    关注