[转] Swoft HTTP 服务
转载自Go语言中文网, https://studygolang.com/articles/20667
传统架构
传统架构中所使用的Nginx + PHP-FPM的模型中,Nginx由于基于Linux的epoll
事件模型一个工作进程worker
会同时去处理多个请求,但是PHP-FPM的工作进程fpm-worker
却只能在同一时刻处理一个请求,而且fpm-worker
工作进程每次处理请求前都需要重新初始化MVC
框架然后再释放资源。当在高并发请求场景下时,fpm-worker
是完全不够用的,此时Nginx会直接响应502。另外,fpm-worker
进程间的切换消耗也很大。
PHP的FastCGI进程管理器PHP-FPM由于本身是同步阻塞进程模型,在请求结束后会释放掉所有资源,包括框架初始化创建的一系列对象,导致PHP进程空转并消耗大量的CPU资源,从而导致单机吞吐能力有限。简单来说就是请求夯住会导致CPU不能释放资源大大浪费CPU使用率。
PHP-FPM进程模型属于预派生子进程模式,即来一个请求就会fork
派生一个进程,进程的开销非常大从而大大降低吞吐率,另外并发量也只能由进程数决定。
预派生子进程模式是指程序启动后会创建多个进程,每个子进程会进入Accept
,等待新的连接进入。当客户端连接到服务器时,其中一个子进程会被唤醒,开始处理客户端请求,并且不再接受新的TCP连接。当此连接关闭时子进程会释放,重新进入Accept
参与处理新的连接。
预派生子进程模式的优势是完全可以复用进程且无需太多的上下文切换,缺点是这种模型严重依赖进程的数量来解决并发问题。由于一个客户端连接需要占用一个进程,工作进程数量有多少并发处理能力就有多少,可是操作系统能够创建的进程数量都是有限的。
PHP框架在初始化时会占用大量计算资源,而每个请求都需要重新进行初始化。当启动大量进程时会带来额外的进程调度消耗,虽然数百个进程出现进程上下文切换调度消耗所占的CPU不足1%可以忽略不计,但同时启动成千上万个进程消耗会直接上升,调度消耗可能占满CPU。
另外,请求一个第三方接口会非常慢,请求过程中会一直占用CPU资源,浪费昂贵的硬件资源。比如即时聊天程序的单机可能要维持数十万的连接,那么也就要启动数十万的进程,这显然是不可能的。那么,有没有 一种技术可以在一个进程内处理所有并发IO呢?答案是采用IO复用技术。
解决方案
那么有什么样的解决方案呢?
通过业务分析不难发现,Web应用中90%以上的都是IO密集型业务,只要提高IO复用的能力就可以提升单机吞吐能力,另外需要将PHP-FPM的同步阻塞模式调整成异步非阻塞模式,也就可以解决核心的性能问题。
如何提升IO复用能力呢?首先需要明白IO多路复用指的是什么,IO多路复用主要解决的问题是如何在一个进程中维持更多的连接数,这里的复用实际上指的是复用的线程。关于IO复用技术的历史其实是和多进程一样长的,很早之前Linux就提供了select
系统调用,它可以在一个进程内维持1024个连接。后来又加入了poll
系统调用,poll
做了一些改进解决了1024个连接限制的问题。但select
和poll
存在的问题是它需要循环检测连接是否有事件。这样问题就来了,如果服务器上有100w个连接,某一时刻只有一个连接向服务器发送了数据,此时select
和poll
就需要做100W次循环,其中只有1次是命中的,剩下的都是无效的,这不白白浪费了CPU的资源吗?直到Linux2.6内核提供了epoll
系统调用才可以维持无限数量的连接,且无需轮询,这才真正解决了C10K问题。
现在各种高并发异步IO的服务器程序都是基于epoll
实现的,比如Nginx
、Node.js
、Erlang
、Golang
... 像Node.js
、Redis
这样单进程单线程的程序都可以维持超过100w的TCP连接,这全部要归功于epoll
技术。
在IO密集型业务中需要频繁的上下文切换,如果采用线程模式开发会太过复杂,另外一个进程中能开的线程数量也是有限的,线程太多会直接增加CPU的负责和内存资源。
线程本身是没有阻塞态的,当IO阻塞时也不会主动让出CPU资源,这种抢占式调度模式不太适合PHP开发。不过可以使用全协程模式让同步代码异步执行来解决这个问题。
为什么要使用Swoole呢?
Swoole的强大之处在于进程模型的涉及,既解决了异步问题又解决了并行。Swoole中提供了完整的协程(Coroutine)和通道(Channel)特性,带来全的CSP编程模型。应用层可以使用完全同步的编程方式,底层将自动实现异步IO。另外,使用常驻内存模式可以避免每次框架的初始化,节约了性能上的开销。
PHP应用的Web架构
- LNMP
Nginx作为Web服务器,PHP-FPM维护一个进程池去运行Web项目。LNMP模型的优点时简单、成熟、稳定,一次运行随后销毁带来的开发便捷性最大的特点。
PHP-FPM引入了进程常驻避免了每次请求创建和销毁进程时的性能开销并拓展了加载的开销,但每个请求仍然要执行PHP RINT于RSHUTDOWN之间的所有流程,包括重新加载依次框架源码和项目代码,造成了极大的性能浪费。
- LNMP + Swoole
LNMP+Swoole 是LNMP的一种变体,是在LNMP的基础上引入了Swoole组件。和PHP-FPM一样,Swoole有一套自己的进程管理机制,由于代码变得高度常驻,编程思维需要从同步转变到异步。所以Swoole和传统基于PHP-FPM的Web框架亲和力很低。因此出现了这种折中方案,并没有直接将原有PHP代码运行在Swoole中,而是使用Swoole搭建了一个服务,而是使用Swoole搭建了一个服务,系统通过接口与Swoole通信,从而为Web项目补充了异步处理能力。
LNMP+Swoole虽然引入了Swoole和异步处理能力,但核心仍然是PHP-FPM,实际上并没有发挥出Swoole的真正优势。
- Swoole HTTP Server
Swoole HTTP Server与LNMP+Swoole相比有着巨大的变化,这种模型中充当Web服务器角色的构件不仅仅有Ngnix,应用本身也包含了一个内建的Web服务器,不过由于Swoole HTTP Server不是专业的HTTP服务器,对HTTP的处理不完善,因此仍然需要使用Nginx作为静态资源服务器及反向代理,Swoole HTTP Server仅处理PHP相关的HTTP流量。
由于Swoole已经包含了Web服务器,不再需要实现CGI或FastCGI的通用网关协议和Web服务器进行通信。另一方面Swoole有自己的进程管理,因此PHP-FPM可以直接被去除了。对于PHP资源而言,Swoole HTTP Server相当于Nginx + PHP-FPM。
Swoole HTTP Server一次加载常驻内存,不同的请求之间复用了onRequest
以外的所有流程,使得每个请求的开销大大降低。异步IO的特性使得这种模型吞吐量远远高于LNMP模型。另外相对独立的Swoole服务,内嵌在Web系统中的Swoole使用更加直接方便,支持更好。
Swoole 与 Swoft 的关系
Swoft与Swoole的关系是什么?
- Swoole是一个异步引擎,核心是为PHP提供异步IO执行的能力,同时提供一套异步编程可能会用到的工具集。
- Swoole HTTP Server是Swoole的一个组件,是Swoole服务器的一种,提供了一个适合Swoole直接运行的HTTP服务器环境。
- Swoft是一个现代的Web框架,和Swoole亲和性高,同时也是Swoole HTTP Server模型的一个实践。
Swoft管理着Swoole和Swoole HTTP Server,对开发者屏蔽Swoole的各种复杂操作细节,并作为一个Web框架向开发者提供了各种Web开发所需的路由、MVC、数据库访问等功能组件等。
Swoft是如何使用Swoole的呢?
Swoft直接使用的是Swoole内建的\Swoole\Http\Server
,HTTP服务器已经处理好了所有HTTP层面的东西,剩下只需考虑关注应用本身。
HTTP服务生命周期
Swoft的HTTP服务是基于\Swoole\Http\Server
实现的协程HTTP服务,Swoft框架层封装了MVC方便编码以获取协程带来的超高性能。
Swoft HTTP服务器启动会根据.env
环境配置中的设置,在使用composer install
安装组件时会自动复制环境变量配置文件.env
,若没有可手工复制.env.sample
并重命名为.env
。
$ .env
# HTTP 服务设置
HTTP_HOST=0.0.0.0
HTTP_PORT=80
HTTP_MODE=SWOOLE_PROCESS
HTTP_TYPE=SWOOLE_SOCK_TCP
HTTP服务器启动命令
// 启动服务,根据.env环境配置决定是否为守护进程方式(daemonize)。
$ php bin/swoft start
// 以后台后台进程方式启动
$ php bin/swoft start -d
// 重启服务
$ php bin/swoft start restart
// 重新加载
$ php bin/swoft reload
// 关闭服务
$ php bin/swoft stop
Swoft框架是建立在Swoole扩展之上运行的,在Swoft服务启动阶段,首先需要关注的是OnWorkStart
事件,此事件会在Worker
工作进程启动的时候触发,这个过程也是Swoft众多机制实现的关键,此时Swoft会进行扫描目录、读取配置、收集注解、收集事件监听器...。然后会根据扫描到的注解信息执行对应的功能逻辑,并存储在与注解对应的Collector
容器内,包括注册路由、注册事件监听器、注册中间件、注册过滤器等。
在Swoole启动前的重要行为特征
- 基础
bootstrap
行为,如必要的常量定义、Composer加载器引入,读取配置。 - 生成被所有
worker/task
进程共享的程序全局期的对象,如Swoole\Lock
、Swoft\Memory\Table
的创建。 - 启动时所有进程中只能执行一次的操作,如前置
Process
的启动。 Bean
容器基本初始化以及项目启动流程需要的coreBean
的加载
和HTTP服务关系最密切的进程是Swoole中Worker进程,绝大部分业务处理都在Worker工作进程中。对于每个Swoole事件,Swoft都提供了对应的Swoole监视器(对应@SwooleListener
注解)作为事件机制的封装。
要理解Swoft的HTTP服务器是如何在Swoole下运行,重点需要关注两个Swoole事件swoole.workerStart
和swoole.onRequest
。
swoole.workerStart事件
workerStart
事件在TaskWorker/Worker
进程启动时发生,每个TaskWorker/Worker
进程里都会执行一次,这是个关键节点,因为swoole.workerStart
回调之后新建的对象都是进程全局期的,使用的内存都属于特定的Task\Worker
进程,相互独立。也只有在这个阶段或以后初始化的部分才是可以被热重载的。
$ vim /vendor/swoft/framework/src/Bootstrap/Server/ServerTrait.php
<?php
namespace Swoft\Bootstrap\Server;
use Swoft\App;
use Swoft\Bean\BeanFactory;
use Swoft\Bean\Collector\ServerListenerCollector;
use Swoft\Bootstrap\SwooleEvent;
use Swoft\Core\ApplicationContext;
use Swoft\Core\InitApplicationContext;
use Swoft\Event\AppEvent;
use Swoft\Helper\ProcessHelper;
use Swoft\Pipe\PipeMessage;
use Swoft\Pipe\PipeMessageInterface;
use Swoole\Server;
/**
* Server trait
*/
trait ServerTrait
{
/**
* OnWorkerStart event callback
*
* @param Server $server server
* @param int $workerId workerId
* @throws \InvalidArgumentException
* @throws \ReflectionException
*/
public function onWorkerStart(Server $server, int $workerId)
{
// Init Worker and TaskWorker
$setting = $server->setting;
$isWorker = false;
if ($workerId >= $setting['worker_num']) {
// TaskWorker
ApplicationContext::setContext(ApplicationContext::TASK);
ProcessHelper::setProcessTitle($this->serverSetting['pname'] . ' task process');
} else {
// Worker
$isWorker = true;
ApplicationContext::setContext(ApplicationContext::WORKER);
ProcessHelper::setProcessTitle($this->serverSetting['pname'] . ' worker process');
}
$this->beforeWorkerStart($server, $workerId, $isWorker);
$this->fireServerEvent(SwooleEvent::ON_WORKER_START, [$server, $workerId, $isWorker]);
}
/**
* @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);
}
}
}
reloadBean
方法作为实践底层关键代码主要完成三件事:
- 初始化Bean容器
BeanFactory::reload()
是Swoft的Bean容器初始化入口,注解的扫描也是在此处进行的,准确来说,Bean容器真正的初始化阶段在Swoole服务器启动前的Bootstrap阶段就已经进行了,只不过那时进行的是少部分的初始化,相对swoole.workerStart
中初始化的Bean数量比重还很小。在workerStart
中初始化Bean容器是Swoft可以热更代码的基础。
- 初始化应用的上下文
initApplicationContext->init()
会注册Swoft事件监听器(对应@Listener
注解),方便用户处理Swoft应用本身的各种钩子。随后触发一个swoft.applicationLoader
事件,各组件通过该事件进行配置文件加载,以及HTTP/RPC路由注册。
- 服务注册
swoole.onRequest事件
Swoft的请求和响应实现了PSR-7,请求和响应对象存在于每次HTTP请求,这里的请求对象Request
指的是Swoft\Http\Message\Server\Request
,响应Response
指的是Swoft\Http\Message\Server\Response
。
每个请求从开始到结束都是由Swoole本身的onRequest
方法或onResponse
方法事件监听并委托给Dispatcher
方法来处理并响应的,Dispatcher
方法的主要职责是负责调度请求生命周期内的各个组件。
HTTP服务中将由ServerDispather
来负责调度,参与者包括RequestContext
、RequestHandler
、ExceptionHandler
。
RequestContext
请求上下文作为当前请求信息的容器将贯穿整个请求生命周期,负责信息的存储和传递。RequestHandler
请求处理器是整个请求生命周期的核心组件,其实也就是个中间件Middleware
,该组件实现了PSR-15协议。- 负责将
Request
=>Route
=>Controller
=>Action
=>Renderer
=>Response
整个请求流程贯穿起来,也就是从请求Request
到响应Response
的过程 - 只要在任意一个环节中返回一个有效的响应对象
Response
就能对该请求做出响应并返回
- 负责将
ExceptionHandler
异常处理器是在遇到异常的情况下出来收拾场面的,确保在各种异常情况下依旧能给客户端返回一个预期内的结果
每个HTTP请求到来时仅仅会触发swoole.onRequest
事件,Swoft框架本身是由大量进程全局期和少量程序全局期的对象构成。onRequest
中创建的对象比如$request
和$response
都是请求期的,随着HTTP请求的结束而回收。
$ vim /vendor/swoft/http-server/src/ServerDispatcher.php
<?php
namespace Swoft\Http\Server;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Swoft\App;
use Swoft\Contract\DispatcherInterface;
use Swoft\Core\ErrorHandler;
use Swoft\Core\RequestContext;
use Swoft\Core\RequestHandler;
use Swoft\Event\AppEvent;
use Swoft\Http\Message\Server\Response;
use Swoft\Http\Server\Event\HttpServerEvent;
use Swoft\Http\Server\Middleware\HandlerAdapterMiddleware;
use Swoft\Http\Server\Middleware\SwoftMiddleware;
use Swoft\Http\Server\Middleware\UserMiddleware;
use Swoft\Http\Server\Middleware\ValidatorMiddleware;
/**
* The dispatcher of http server
*/
class ServerDispatcher implements DispatcherInterface
{
/**
* Do dispatcher
*
* @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
事件。
在HTTP服务器的生命周期中需要重点理解
- Swoole的Worker进程是绝大多数HTTP服务代码的运行环境
- 部分初始化和加载操作在Swoole服务器启动前完成,部分在
swoole.workerStart
事件回调中完成,前者无法热重载但可以被多个进程共享。 - 初始化代码只会在系统启动和Worker/Task进程启动时执行一次,不像PHP-FPM每次请求都会执行一次,框架对象不像PHP-FPM会请求返回而销毁。
- 每次请求都会触发一次
swoole.onRequest
事件,事件中是请求处理代码真正运行的位置,只有事件内产生的对象才会在请求结束时被回收。
[转] Swoft HTTP 服务的更多相关文章
- Swoft 2.0.3 重大更新,发布优雅的微服务治理
 什么是 Swoft ? Swoft 是一款基于 Swoole 扩展实现的 PHP 微服务协程框架.Swoft 能像 Go 一样,内置协程网络服务器及常用的协程客户端且常驻内存,不依赖传统的 PHP ...
- swoft 源码解读【转】
官网: https://www.swoft.org/ 源码解读: http://naotu.baidu.com/file/814e81c9781b733e04218ac7a0494e2a?toke ...
- Swoft 新手向教程 - 通过 Docker 搭建一个开发环境
本系列文章将从使用层面介绍 Swoft 框架的使用及业务开发,面向初中级的 PHPer Swoft首个基于 Swoole 原生协程的新时代 PHP 高性能协程全栈组件化框架,内置协程网络服务器及常用的 ...
- 如何通过Docker搭建一个swoft开发环境
本篇文章给大家分享的内容是关于如何通过Docker搭建一个swoft开发环境 ,内容很详细,有需要的朋友可以参考一下,希望可以帮助到你们. Swoft首个基于 Swoole 原生协程的新时代 PHP ...
- php架构之路
鉴于最近跟小伙伴聊了很多PHP架构发展方向的问题,相关技术整理了一下,也顺便规划了一下自己的2019年. 一.常用的设计模式以及使用场景 以下是我用到过的 工厂,单例,策略,注册,适配,观察者,原 ...
- PHP高级进阶之路
一:常见模式与框架 学习PHP技术体系,设计模式,流行的框架 常见的设计模式,编码必备 Laravel.ThinkPHP开发必不可少的最新框架 YII.Symfony4.1核心源码剖析 二:微服务架构 ...
- [手把手教你] 用Swoft 搭建微服务(TCP RPC)
序言 Swoft Framework 基于 Swoole 原生协程的新时代 PHP 全栈式协程框架 Swoft 是什么? Swoft 框架是首个基于Swoole 原生协程的新时代 PHP高性能协程全栈 ...
- Swoole和Swoft的那些事 (Http/Rpc服务篇)
https://www.jianshu.com/p/4c0f625d5e11 Swoft在PHPer圈中是一个门槛较高的Web框架,不仅仅由于框架本身带来了很多新概念和前沿的设计,还在于Swoft是一 ...
- Swoft 源码剖析 - Swoole和Swoft的那些事 (Http/Rpc服务篇)
前言 Swoft在PHPer圈中是一个门槛较高的Web框架,不仅仅由于框架本身带来了很多新概念和前沿的设计,还在于Swoft是一个基于Swoole的框架.Swoole在PHPer圈内学习成本最高的工具 ...
随机推荐
- CSS特效(一)
三角形 <!-- log --> <div class="tri"></div> <style> .tri { width: 0; ...
- 【系统之音】Android进程的创建及启动简述
Android系统中的进程(这里不包括init等底层的进程)都是通过Zygote fork而来的,那这些进程的启动流程都是怎样的呢? 这里将Android进程分为两个部分: (1)系统框架进程Syst ...
- 跨平台框架与React Native基础
跨平台框架 什么是跨平台框架? 这里的多个平台一般是指 iOS 和 Android . 为什么需要跨平台框架? 目前,移动开发技术主要分为原生开发和跨平台开发两种.其中,原生应用是指在某个特定的移动平 ...
- (专题一)06 MATLAB的算术运算
基本算术运算 乘法运算:A的行数等于B的列数(A,B两矩阵维数和大小相容) 除法运算 逻辑运算 真为1,假为0 优先级,算术运算的优先级最高,逻辑运算的优先级最低,但逻辑非运算是单目运算,他的优先级比 ...
- EAM资产管理系统应用趋势简述
EAM资产管理系统应用趋势简述 随着各种企业对资产管理需求的不断增长,EAM市场也正在持续升温,对于石油.化工.煤炭.钢铁等流程化企业及其他资产密集型企业来说,设备占用了企业大量的成本,如何降低设备维 ...
- 《Java从入门到失业》第四章:类和对象(4.5):包
4.5包 前面我们已经听过包(package)这个概念了,比如String类在java.lang包下,Arrays类在java.util包下.那么为什么要引入包的概念呢?我们思考一个问题:java类库 ...
- 刷题[BJDCTF2020]Mark loves cat
解题思路 打开网页,发现是一个博客,基本寻找博客挂载信息,源码等无果后,扫描后台.发现.git泄露 .git泄露 发现.git泄露后,使用Git Extract这款工具,可自动将源码clone到本地 ...
- 从源码角度理解Java设计模式——装饰者模式
一.饰器者模式介绍 装饰者模式定义:在不改变原有对象的基础上附加功能,相比生成子类更灵活. 适用场景:动态的给一个对象添加或者撤销功能. 优点:可以不改变原有对象的情况下动态扩展功能,可以使扩展的多个 ...
- 你用对锁了吗?浅谈 Java “锁” 事
每个时代,都不会亏待会学习的人 大家好,我是yes. 本来打算继续写消息队列的东西的,但是最近在带新同事,发现新同事对于锁这方面有一些误解,所以今天就来谈谈"锁"事和 Java 中 ...
- 音频数据增强及python实现
博客作者:凌逆战 博客地址:https://www.cnblogs.com/LXP-Never/p/13404523.html 音频时域波形具有以下特征:音调,响度,质量.我们在进行数据增强时,最好只 ...