Swoft 源码剖析 - Swoole和Swoft的那些事 (Http/Rpc服务篇)
source link: https://segmentfault.com/a/1190000015603847?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
作者:bromine
链接: https://www.jianshu.com/p/4c0...
來源:简书
著作权归作者所有,本文已获得作者授权转载,并对原文进行了重新的排版。
Swoft Github: https://github.com/swoft-clou...
Swoft源码剖析系列目录: https://segmentfault.com/a/11...
前言
Swoft
在 PHPer
圈中是一个门槛较高的 Web
框架,不仅仅由于框架本身带来了很多新概念和前沿的设计,还在于 Swoft
是一个基于 Swoole
的框架。 Swoole
在 PHPer
圈内学习成本最高的工具没有之一,虽然 Swoft
的出现降低了 Swoole
的使用成本,但如果你对 Swoole
本身了解不够深入,仍然很难避免栽进种种 "坑" 中。
考虑到这个现状,也为降低阅读难度,后续几个和 Swoole
联系较为密切的机制,笔者会调整写作思路,将文章的定位从 「帮助读者深入理解Swoft」 调整为 「帮助读者理解Swoft和Swoole」 ,叙述节奏也会放慢。
三种PHP应用的Web模型
LNMP
和 LAMP
是绝大多数 PHPer
最熟悉的基础Web架构,这里以常见的 LNMP
作为例子描述一个常见 无Swoole
应用的构件组成: Nginx
充当 Web Service
, PHP-FPM
维护一个进程池去运行 Web
项目。
对比更古老的 CGI
模型, PHP-FPM
已经引入了进程常驻的概念,避免每次请求创建并销毁进程的开销以及拓展加载的开销,但是每个请求仍然要执行PHP RINIT
与 RSHUTDOWN
之间的所有流程,包括重新加载一次框架源码以及项目代码,造成极大的性能浪费。
这种模型的优点是简单成熟和稳定, 一次运行随后销毁 带来的开发便捷性是 PHP
能够流行起来的原因之一。市面上绝大多数 PHP
项目使用的都是基于该种架构的变体。
LNMP-with-Swoole
是 LNMP
的一种变体,其在 LNMP
的基础上引入了 Swoole
组件。
和 PHP-FPM
一样, Swoole
有一套自己的进程管理机制。但由于代码变得高度常驻和编程思维需要从同步到异步的转变,所以 Swoole
和传统的基于 PHP-FPM
的 Web
框架亲和度很低,即使是适配升级过的老式 Web
框架,目前在 Swoole
上运行的表现往往并不好。
因此出现了这在这种折中方案,并没有直接将原有 PHP
代码运行在 Swoole
中,而是使用 Swoole
搭建了一个服务,系统通过接口与 Swoole
通信,从而为 Web
项目补充了异步处理的能力。我称呼这种同时使用 PHP-FPM
和 Swoole
的系统为 半 Swoole
应用。因为接入简单,所以是绝大多数现有项目优先考虑的Swoole接入方案。
LNMP-with-Swoole模型虽然引入了 Swoole
和异步处理能力,但是核心还是 PHP-FPM
,实际上还远远没有发挥出 Swoole
的真正优势。
Swoole-HTTP-Server
和 LNMP-with-Swoole
相比有巨大的变化,这种模型中充当 Web Server
角色的构件不仅仅有 Nginx
,应用本身也包含了一个内建 Web Server
,不过由于 Swoole Http Server
不是专业的 HTTP Server
,对 Http
的处理不完善 ,因此仍然需要使用Nginx作为静态资源服务器以及反代, Swoole HTTP Server
仅仅处理 PHP
相关的 HTTP
流量。
一方面由于 Swoole
已经包含了 WebServer
,不再需要实现 CGI
或者 Fast-CGI
的通用协议去和 Web Server
通信,另一方面 Swoole
有自己的进程管理,因此 PHP-FPM
可以直接被去除了。对于 PHP
资源而言,在这种模型中, Swoole Http Server
的地位相当于传统模型中的 Nginx
和 PHP-FPM
之和。
一次加载常驻内存,不同的请求间基本上复用了 onRequest 以外的所有流程,使得每个请求的开销大大降低; 异步IO
的特性使得这种模型 吞吐量远远高于传统的 LNMP模型
。另外相对于独立的 Swoole
服务,内嵌在 Web
系统中的 Swoole
使用更加的直接方便,支持更好。
Swoft 和 Swoole 的关系是什么 ?
-
Swoole
是一个异步引擎,核心是为PHP
提供异步IO
执行的能力 ,同时提供一套异步编程可能会用到的工具集。 -
Swoole-HTTP-Server
是Swoole
的一个组件,是SwooleServer
中的一种,提供了一个适合Swoole
直接运行的HttpServer
环境。 -
Swoft
一个现代的Web框架
, 和Swoole
亲和性高 ,同时也是上面提到的Swoole-HTTP-Server
模型的一个实践。
Swoft
管理着该 Web
模型中的 Swoole
,以及 Swoole-HTTP-Server
, 对开发者屏蔽 Swoole
的种种复杂操作细节 ,并作为一个 Web框架
向开发者提供各种 Web开发
需要用到的 路由
, MVC
, 数据库访问
等功能组件等。
Swoft 是如何使用 Swoole 的 ?
最核心的就是 HttpServer
以及 RpcServer
HTTP 服务器
Swoft
直接使用的是 Swoole
内建的 \Swoole\Http\Server
,它已经处理好所有 HTTP
层面的所有东西,我们只需要关注应用本身,我们来看一下 HTTP
服务几个重要生命周期点。
Swoole 启动前
这个阶段进行的行为有几个特征
- 基础
bootstrap
行为:如必须的常量定义,Composer
加载器引入,配置读取等; - 需要生成被所有
Worker/Task
进程共享的程序全局期的对象,如Swoole\Lock
,Swoft\Memory\Table
的创建; - 启动时,所有进程中合计只能执行一次的操作:如前置
Process
的启动; -
Bean
容器基本初始化,以及项目启动流程需要的coreBean
的加载。
这块涉及东西比较杂,为控制篇幅后续用单独文章介绍。
和 Http
服务关系最密切的进程是 Swoole
中的 Worker进程(组)
,绝大部分业务处理都在该进程中进行。
对于每个 Swoole事件
, Swoft
都提供了对应的 Swoole监听器
(对应 @SwooleListener
注解)作为事件机制的封装。要理解 Swoft
的 HttpServer
是如何在 Swoole
下运行的我们重点需要关注下两个在两个 Swoole
事件 swoole.workerStart
和 swoole.onRequest
。
swoole.workerStart
事件
WorkerStart
事件在 TaskWorker/Worker
进程启动时发生,每个 TaskWorker/Worker
进程里都会执行一次。
这是个关键节点,因为 swoole.workerStart
回调之后新建的对象都是进程全局期的,使用的内存都属于特定的 Task/Worker
进程,相互独立。也只有在这个阶段或以后初始化的部分才是可以被热重载的。
事件底层关键代码如下:
// Swoft\Bootstrap\Server\ServerTrait.php /** * @param bool $isWorker * @throws \InvalidArgumentException * @throws \ReflectionException */ protected function reloadBean(bool $isWorker) { BeanFactory::reload(); $initApplicationContext = new InitApplicationContext(); $initApplicationContext->init(); if($isWorker && $this->workerLock->trylock() && env('AUTO_REGISTER', false)){ App::trigger(AppEvent::WORKER_START); } }
这里做的事情有3点
- 初始化
Bean
容器:
上文中的BeanFactory::reload();
就是Swoft
的Bean
容器初始化入口,注解的扫描也是在此处进行(实际上这个说法并不准确,Bean
容器真正的初始化阶段在Swoole Server
启动前的BootStrap
阶段就已经进行了,只不过那时进行的是少部分初始化,相对swoole.workerStart
中的初始化的Bean
数量,比重很小)。在workerStart
中初始化Bean
容器是Swoft
可以热更新代码的基础。 - 初始化的应用上下文
initApplicationContext->init()
会注册Swoft
事件监听器(对应@Listener
),方便用户处理Swoft
应用本身的各种钩子。随后触发一个swoft.applicationLoader
事件,各组件通过该事件进行配置文件加载,HTTP/RPC
路由注册。 - 服务注册
具体内容会在服务治理章节讲述。
swoole.onRequest
事件
每个 HTTP
请求到来时仅仅会触发 swoole.onRequest
事件。
框架代码本身都是由大量进程全局期和少量程序全局期的对象构成,而 onReceive
中创建的对象譬如 $request
和 $response
都是请求期的,随着 HTTP
请求的结束而回收。
事件底层关键代码如下:
/** * @param array ...$params * @return \Psr\Http\Message\ResponseInterface * @throws \InvalidArgumentException */ public function dispatch(...$params): ResponseInterface { /** * @var RequestInterface $request * @var ResponseInterface $response */ list($request, $response) = $params; try { // before dispatcher $this->beforeDispatch($request, $response); // request middlewares $middlewares = $this->requestMiddleware(); $request = RequestContext::getRequest(); $requestHandler = new RequestHandler($middlewares, $this->handlerAdapter); $response = $requestHandler->handle($request); } catch (\Throwable $throwable) { /* @var ErrorHandler $errorHandler */ $errorHandler = App::getBean(ErrorHandler::class); $response = $errorHandler->handle($throwable); } $this->afterDispatch($response); return $response; }
-
beforeDispatch($request, $response):
设置请求上下文,并触发一个swoft.beforeRequest
事件。 -
RequestHandler->handle($request):
执行各个 中间件 和请求对应的Action
,具体处理可以参考RPC章节,原理基本相同。 -
$afterDispatch($response):
整理HTTP
响应报文发送客户端并触发swoft.resourceRelease
(详情在连接池一文中提及)事件和swoft.afterRequest
事件
总的来说,纵观这几个生命周期点你需要搞清楚几件事:
-
Swoole
的Worker
进程是你绝大多数HTTP
服务代码的运行环境。 - 一部分初始化和加载操作在
Swoole
的Server
启动前完成,一部分在swoole.workerStart
事件回调中完成,前者无法热重载但可能被多个进程共享。 - 初始化代码只会在系统启动和
Worker/Task
进程启动时执行一次, 不像PHP-FPM
每次请求都会执行一次,框架对象也不像PHP-FPM
会随请求返回而销毁。 - 每次请求都会触发一次
swoole.onRequest
事件,里面就是我们的请求处理代码真正运行的地方,只有这事件内产生的对象才会在请求结束时被回收。
RPC服务器
生命周期和 HTTP服务
基本一致,详情参考 《Swoft源码剖析-RPC功能实现》
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK