Swoft 源码剖析 - Swoole和Swoft的那些事 (Http/Rpc服务篇)
前言
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。
$afterDispatch($response):
整理HTTP
响应报文发送客户端并触发swoft.resourceRelease
事件和swoft.afterRequest
事件
总的来说,纵观这几个生命周期点你需要搞清楚几件事:
Swoole
的Worker
进程是你绝大多数HTTP
服务代码的运行环境。- 一部分初始化和加载操作在
Swoole
的Server
启动前完成,一部分在swoole.workerStart
事件回调中完成,前者无法热重载但可能被多个进程共享。 - 初始化代码只会在系统启动和
Worker/Task
进程启动时执行一次, 不像PHP-FPM
每次请求都会执行一次,框架对象也不像PHP-FPM
会随请求返回而销毁。 - 每次请求都会触发一次
swoole.onRequest
事件,里面就是我们的请求处理代码真正运行的地方,只有这事件内产生的对象才会在请求结束时被回收。
RPC服务器
生命周期和
HTTP服务
基本一致以上是文章全部内容,有需要学习交流的友人请加入Swoole交流群的咱们一起,有问题一起交流,一起进步!前提是你是学技术的。感谢阅读!
Swoft 源码剖析 - Swoole和Swoft的那些事 (Http/Rpc服务篇)的更多相关文章
- Swoft源码之Swoole和Swoft的分析
这篇文章给大家分享的内容是关于Swoft 源码剖析之Swoole和Swoft的一些介绍(Task投递/定时任务篇),有一定的参考价值,有需要的朋友可以参考一下. 前言 Swoft的任务功能基于Swoo ...
- Swoole和Swoft的那些事 (Http/Rpc服务篇)
https://www.jianshu.com/p/4c0f625d5e11 Swoft在PHPer圈中是一个门槛较高的Web框架,不仅仅由于框架本身带来了很多新概念和前沿的设计,还在于Swoft是一 ...
- swoft| 源码解读系列一: 好难! swoft demo 都跑不起来怎么破? docker 了解一下呗~
title: swoft| 源码解读系列一: 好难! swoft demo 都跑不起来怎么破? docker 了解一下呗~description: 阅读 sowft 框架源码, swoft 第一步, ...
- swoft| 源码解读系列二: 启动阶段, swoft 都干了些啥?
date: 2018-8-01 14:22:17title: swoft| 源码解读系列二: 启动阶段, swoft 都干了些啥?description: 阅读 sowft 框架源码, 了解 sowf ...
- jQuery之Deferred源码剖析
一.前言 大约在夏季,我们谈过ES6的Promise(详见here),其实在ES6前jQuery早就有了Promise,也就是我们所知道的Deferred对象,宗旨当然也和ES6的Promise一样, ...
- Nodejs事件引擎libuv源码剖析之:高效线程池(threadpool)的实现
声明:本文为原创博文,转载请注明出处. Nodejs编程是全异步的,这就意味着我们不必每次都阻塞等待该次操作的结果,而事件完成(就绪)时会主动回调通知我们.在网络编程中,一般都是基于Reactor线程 ...
- Apache Spark源码剖析
Apache Spark源码剖析(全面系统介绍Spark源码,提供分析源码的实用技巧和合理的阅读顺序,充分了解Spark的设计思想和运行机理) 许鹏 著 ISBN 978-7-121-25420- ...
- 基于mybatis-generator-core 1.3.5项目的修订版以及源码剖析
项目简单说明 mybatis-generator,是根据数据库表.字段反向生成实体类等代码文件.我在国庆时候,没事剖析了mybatis-generator-core源码,写了相当详细的中文注释,可以去 ...
- STL"源码"剖析-重点知识总结
STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...
随机推荐
- 百万年薪python之路 -- 并发编程之 多线程 一
多线程 1.进程: 生产者消费者模型 一种编程思想,模型,设计模式,理论等等,都是交给你一种编程的方法,以后遇到类似的情况,套用即可 生产者与消费者模型的三要素: 生产者:产生数据的 消费者:接收数据 ...
- PassWord控件
<StackPanel Margin="> <Label>Text:</Label> <TextBox /> <Label>Pas ...
- 关于在vue-cli脚手架中使用CDN引入element-ui不成功的坑
在前端开发过程中,为了减少最后打包出来的体积,我们会用到cdn引入一些比较大的库来解决. 常见我们引入的element-ui库,在最近使用cdn引入时,无论如何都引入不成功,其他的如Vue.vue-r ...
- 关于ESET占用CPU严重 的解决方案||ESET CPU 100%||用迅雷时ESET占用CPU(6月22日再次更新)
关于ESET占用CPU严重 的解决方案 本文根据原帖有适量删改. ESET 的杀毒软件历来以占用资源少,CPU消耗少著称,可是很多朋友(特别是中国大陆的朋友)反应ESS & EAV 间歇性占用 ...
- JVM(9) 程序编译及代码优化
一.早期(编译器)优化 1.编译期 Java 语言的 “编译期” 其实是一段 “不确定” 的操作过程,因为它可能是指 一个前端编译器(其实叫 “编译器的前端” 更准确一些)把 *.java 文件转变成 ...
- Spring(三)面向切面编程(AOP)
在直系学长曾经的指导下,参考了直系学长的博客(https://www.cnblogs.com/WellHold/p/6655769.html)学习Spring的另一个核心概念--面向切片编程,即AOP ...
- 数字麦克风PDM信号采集与STM32 I2S接口应用(三)
本文是数字麦克风笔记文章的数据处理篇. 读取数字麦克风的信号,需要嵌入式驱动和PC应用的结合,驱动负责信号采集,应用代码负责声音分析. 一般而言,在完成特征分析和实验之后,把优化过的代码固化到嵌入式端 ...
- .NET Core 3.1 编写混合 C++ 程序
前言 随着 .NET Core 3.1 的第二个预览版本发布,微软正式将 C++/CLI 移植到 .NET Core 上,从此可以使用 C++ 编写 .NET Core 的程序了. 由于目前仅有 MS ...
- DB2中的MQT优化机制详解和实践
MQT :物化查询表.是以一次查询的结果为基础 定义创建的表(实表),以量取胜(特别是在百万,千万级别的量,效果更显著),可以更快的查询到我们需要的结果.MQT有两种类型,一种是系统维护的MQT , ...
- Project Euler 54: Poker hands
在纸牌游戏中,一手包含五张牌并且每一手都有自己的排序,从低到高的顺序如下: 大牌:牌面数字最大 一对:两张牌有同样的数字 两对:两个不同的一对 三条:三张牌有同样的数字 顺子:所有五张牌的数字是连续的 ...