标题:【Swoole系列6.5】Hyperf中的其它事项
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