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

2023-03-03 13:45:12 浏览数 (2)

Hyperf中的其它事项

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

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

Nginx部署

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

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

代码语言:javascript复制
# 至少需要一个 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 时,它就会自动再把它重启或者拉起新的进程,从而达到进程自动恢复的目的。

代码语言:javascript复制
# 安装 epel 源,如果此前安装过,此步骤跳过
yum install -y epel-release
yum install -y supervisor  

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

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

代码语言:javascript复制
vim /etc/supervisord.d/hyperf.ini

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

代码语言:javascript复制
# 新建一个应用并设置一个名称,这里设置为 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 了。

代码语言:javascript复制
supervisord -c /etc/supervisord.d/supervisord.conf

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

代码语言:javascript复制
[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 应用服务。

代码语言:javascript复制
[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 进程的突然中断。下面的一些命令行命令大家也可以了解一下。

代码语言:javascript复制
# 启动 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 命令行时也接触过。

代码语言:javascript复制
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 服务对象。

代码语言:javascript复制
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 属性哦。

代码语言:javascript复制
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() 方法的调用,感觉离胜利不远了哦。

代码语言:javascript复制
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 下面的各种服务器。

代码语言:javascript复制
// vendor/swoole/ide-helper/src/swoole/Swoole/Http/Server.php
class Server extends SwooleServer
{
}

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

请求与协程生命周期

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

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

总结

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

测试代码:

https://github.com/zhangyue0503/swoole/tree/main/6.框架/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

0 人点赞