深入 Hyperf:HTTP 服务启动时发生了什么

科技资讯 投稿 23000 0 评论

深入 Hyperf:HTTP 服务启动时发生了什么

php bin/hyperf.php start 启动命令,等上几秒钟,就可以看到终端输出的 Worker 进程已启动,HTTP 服务监听在 9501 端口的日志信息。

[INFO] Worker#3 started.  
[INFO] Worker#1 started.  
[INFO] Worker#2 started.  
[INFO] Worker#0 started.  
[INFO] HTTP Server listening at 0.0.0.0:9501  

打开浏览器访问 http://127.0.0.1:9501,不出意外的话,页面会显示 Hello Hyperf,说明 HTTP 服务已经在工作了。那么这是怎么做到的呢?当我们执行启动命令后,Hyperf 是如何让 HTTP 服务启动的?

    Hyperf 启动时会做哪些初始化操作?
  • HTTP 服务启动时会做哪些初始化操作?
  • HTTP 服务初始化时有哪些关键配置项?

接下来,我们就从 Hyperf 的入口文件开始,了解启动 HTTP 服务的实现思路。

bin/hyperf.php 文件:Hyperf 的入口

在启动命令中,除了 PHP 可执行文件以外,有两个是我们要关注的重点:

    bin/hyperf.php:Hyperf 的入口文件
  • start:启动命令的参数

初始化项目配置信息

在这个阶段中,主要是通过调用一些 PHP 内置函数,完成 PHP 相关的配置初始化,比如运行内存大小限制、错误级别、时区等等。

    BASE_PATH:保存的是 Hyperf 项目所在目录的完整路径,Hyperf 中很多操作都是基于该常量来定位目录和文件的路径。
  • SWOOLE_HOOK_FLAGS:Swoole 采用 Hook 原生 PHP 函数的方式实现协程客户端,该常量保存的是 Hook 的函数的范围,比如套接字、文件、curl 等等。SWOOLE_HOOK_ALL 表示 Hook 所有函数。

我们经常会在 Swoole 相关的资料文档中看到「一键协程化」技术,实际上指的就是在启用协程时传入 SWOOLE_HOOK_ALL 配置项,通过 Hook 所有函数,让项目中会发生 IO 阻塞的代码变成可以协程调度的异步 IO,即一键协程化。

初始化类加载器

在初始化类加载器过程中,主要会进行以下操作:

    收集注解使用信息并完成注解收集器的初始化。
  • 生成代理类,为实现 AOP 及 Inject 注解注入功能做准备工作。
  • 生成运行时缓存,提高框架启动速度。

初始化依赖注入容器

初始化命令行应用

我们回过头来看一下启动命令,你会发现,实际上 Hyperf 本身就是一个命令行应用,而启动命令中的 start 不过是命令行应用的参数,也就是要执行的命令的名字。

初始化并启动 HTTP 服务

到了这里,Hyperf 的初始化工作就已经结束了,命令行应用就会开始对启动命令中的参数进行解析,通过参数找到在命令行应用中注册的命令并执行。参数 start 对应的命令类是 StartServer,你可以在 hyperf/server 组件中找到它。

检查运行环境

我们知道,Hyperf 目前使用 Swoole 作为底层框架,所以在启动的时候,会先检查是否安装了 Swoole 的扩展,然后再检查是否禁用了 Swoole 的函数短名(short function name),如果没有禁用,就会输出提示信息并终止程序的运行。

读取服务配置

config/autoload/server.php 文件来配置服务信息,详细的字段说明可以查看 Hyperf 官方文档。

server.type 和 server.servers.type,很多人不太清楚这两个配置项的作用和区别,下面我们来了解一下。

    协程风格的服务端可以在运行时动态创建、销毁,而异步风格的服务端在启动后就不能再对它进行操作了。
  • 协程风格的服务端对连接的处理是在单独的协程中完成,与客户端的交互是顺序性的,而异步风格的服务端无法保证顺序性。

Hyperf 作为上层框架,当然要支持这两种风格的服务端,同时还要考虑到扩展性,方便后续接入其它风格的服务端。

ServerInterface 接口,在接口中定义了三个常量,作为服务类型的枚举值。用于在配置文件中通过 server.servers.type 配置项设置服务的类型。同时,还定义了构造函数、初始化、启动三个方法。

{      
// HTTP 服务    
    public const SERVER_HTTP = 1;      
// Websocket 服务    
    public const SERVER_WEBSOCKET = 2;      
// TCP 服务    
    public const SERVER_BASE = 3;      
// 构造函数    
    public function __construct(ContainerInterface $container, LoggerInterface $logger, EventDispatcherInterface $dispatcher;      
// 初始化    
    public function init(ServerConfig $config: ServerInterface;      
// 启动    
    public function start(: void;  
}  ```  
  
Hyperf 不仅实现了基于 Swoole 的两种风格的服务端,还实现了基于 Swow 的服务端。  
  
- Hyperf\\Server\\Server:异步风格的服务端,由 Swoole 提供底层支持。  
- Hyperf\\Server\\CoroutineServer:协程风格的服务端,由 Swoole 提供底层支持。  
- Hyperf\\Server\\SwowServer:协程风格的服务端,由 Swow 提供底层支持。  
  
我们可以通过 `server.type` 配置项,来决定使用哪种风格的服务端运行各种类型的服务。当然,你也可以通过实现 `ServerInterface` 接口,自定义其它类型的服务端。  
  
### 初始化 HTTP 服务  
  
通过上面的内容你可以知道,在运行 Hyperf 的时候,只能使用一种服务端,但是可以运行多个不同类型的服务,比如 HTTP 服务、Websocket 服务等等。为了便于说明,我会使用异步风格服务端给你介绍初始化 HTTP 服务的过程。  
  
初始化 HTTP 服务的操作,是在 ServerFactory::configure 方法中完成的,主要可以分为两个步骤。  
  
- 第一步,将配置信息解析成 ServerConfig 对象。  
  
在这一步骤中,主要是将配置文件中数组形式的配置信息,解析成 ServerConfig 对象。  
  
```php  
class ServerConfig implements Arrayable  
{  
    public function __construct(protected array $config = []    {       // 将各种类型的服务解析成 Port 对象  
        $servers = [];        foreach ($config['servers'] as $item {            $servers[] = Port::build($item;        }  
      // 将其它类型的配置都保存到对象中  
        $this->setType($config['type'] ?? Server::class            ->setMode($config['mode'] ?? 0            ->setServers($servers            ->setProcesses($config['processes'] ?? []            ->setSettings($config['settings'] ?? []            ->setCallbacks($config['callbacks'] ?? [];    }}  

当没有设置服务端的类型时,默认使用 Hyperf\Server\Server,即异步类型的服务端。

    第二步,调用 Hyperf\Server\Server::init 方法完成对 HTTP 服务的初始化。

    Swoole\Server:TCP 服务器,是所有异步风格服务器的基类。
  • Swoole\Http\Server:HTTP 服务器。
  • Swoole\WebSocket\Server:WebSocket 服务器。

在 init 方法中,会根据 server.servers.type 配置项的值(即 ServerInterface 接口中的常量),实例化出相应的服务器对象,并保存到 server 属性中。

server.servers 配置项中,我们可以配置多个不同类型的服务,那么是如何支持运行多个服务的呢?

下面我们来看一下 init 方法的主要逻辑。

然后,依次遍历这些服务,完成对每个服务的初始化。循环中包括两个分支:

    第一个分支对应了 server 属性未初始化的情况。 此时,会调用 makeServer 方法实例化出相应的服务器对象,然后为服务器对象注册事件回调函数,最后初始化服务器对象的配置信息。
  • 第二个分支对应了 server 属性已初始化的情况。 此时,会调用服务器对象的 addListener 方法,增加一个端口并返回子服务器对象,然后为子服务器对象注册事件回调函数,最后初始化子服务器对象的配置信息。

switch ($type {    
    case ServerInterface::SERVER_HTTP:    
        return new Swoole\Http\Server($host, $port, $mode, $sockType;    
    case ServerInterface::SERVER_WEBSOCKET:    
        return new Swoole\WebSocket\Server($host, $port, $mode, $sockType;    
    case ServerInterface::SERVER_BASE:    
return new Swoole\Server($host, $port, $mode, $sockType;  }  

Swoole 提供了很多事件,比如 workerStart 工作进程启动后的事件、request 收到请求后的事件,这些事件在 Hyperf\Server\Event 中都有相应的常量。

    全局事件:使用 server.callbacks 配置项设置全局的事件的回调函数。
  • 服务事件:使用 server.servers.callbacks 配置项为每一个服务单独设置事件的回调函数。
  • 默认事件:在 Hyperf\Server\Server 对象的 defaultCallbacks 方法中配置了一些默认的事件的回调函数。

这些配置优先级是:服务事件 > 全局事件 > 默认事件。下面的代码展示了注册事件的回调函数的核心逻辑。

// 按照优先级获取配置的所有事件及其回调函数  
$callbacks = array_replace($this->defaultCallbacks(, $config->getCallbacks(, $callbacks;  
foreach ($callbacks as $event => $callback {  
   // 非 Swoole 事件,直接跳过  
    if (! Event::isSwooleEvent($event {        continue;    }    ...    // 为服务器对象注册该事件的回调函数  
    $server->on($event, $callback;}  

启动 HTTP 服务

在启动 HTTP 服务之前,会执行以下代码设置一键协程化 Hook 的函数范围,swoole_hook_flags 函数的返回值就是 SWOOLE_HOOK_FLAGS 常量的值,即 SWOOLE_HOOK_ALL。

Coroutine::set(['hook_flags' => swoole_hook_flags(];  

接着会调用 ServerFactory::start 方法启动服务,在该方法中,直接调用 Hyperf\Server\Server 的 start 方法启动 Swoole 服务器。

在 onWorkerStart 方法中,输出 Worker#{$workerId} started. 日志信息,并通过事件分发器分发 AfterWorkerStart 事件,在该事件的监听器 AfterWorkerStartListener 中,输出 HTTP Server listening at 0.0.0.0:9501 日志信息。

总结

在这篇文章中,我们通过 bin/hyperf.php 文件,了解了 Hyperf 在初始化框架时会执行那些操作。接着,又通过 StartServer 了解了 HTTP 服务在启动过程中的四个步骤。其中,HTTP 服务的初始化是整个启动过程中的关键步骤,你可以配合源码进一步了解 Hyperf 的设计和实现思路。

因此,掌握 HTTP 服务的启动过程,不仅有助于你了解 HTTP 服务的运行细节,还有助于你了解 Hyperf 以及其它类型服务的运行细节。当你遇到问题时,可以按照启动过程中的步骤逐步检查,从而帮助你更快地解决问题。

编程笔记 » 深入 Hyperf:HTTP 服务启动时发生了什么

赞同 (96) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽