Swoft在PHPer圈中是一個(gè)門檻較高的Web框架俐镐,不僅僅由于框架本身帶來(lái)了很多新概念和前沿的設(shè)計(jì)启搂,還在于Swoft是一個(gè)基于Swoole的框架。Swoole在PHPer圈內(nèi)學(xué)習(xí)成本最高的工具沒(méi)有之一羡鸥,雖然Swoft的出現(xiàn)降低了Swoole的使用成本曾沈,但如果你對(duì)Swoole本身了解不夠深入,仍然很難避免栽進(jìn)種種"坑"中烙博。
考慮到這個(gè)現(xiàn)狀瑟蜈,也為降低閱讀難度烟逊,后續(xù)幾個(gè)和Swoole聯(lián)系較為密切的機(jī)制,筆者會(huì)調(diào)整寫作思路铺根,將文章的定位從 「幫助讀者深入理解Swoft」 調(diào)整為 「幫助讀者理解Swoft和Swoole」宪躯,敘述節(jié)奏也會(huì)放慢。
三種PHP應(yīng)用的Web模型
LNMP和LAMP是絕大多數(shù)PHPer最熟悉的基礎(chǔ)Web架構(gòu)位迂,這里以常見(jiàn)的LNMP作為例子描述一個(gè)常見(jiàn) 無(wú)Swoole應(yīng)用的構(gòu)件組成:Nginx充當(dāng)Webservice,PHP-fpm維護(hù)一個(gè)進(jìn)程池去運(yùn)行Web項(xiàng)目访雪。
對(duì)比更古老的cgi模型,php-fpm已經(jīng)引入了進(jìn)程常駐的概念,避免每次請(qǐng)求創(chuàng)建并銷毀進(jìn)程的開(kāi)銷以及拓展加載的開(kāi)銷掂林,但是每個(gè)請(qǐng)求仍然要執(zhí)行PHP RINIT 與 RSHUTDOWN 之間的所有流程臣缀,包括重新加載一次框架源碼以及項(xiàng)目代碼,造成極大的性能浪費(fèi)泻帮。
這種模型的優(yōu)點(diǎn)是簡(jiǎn)單成熟和穩(wěn)定精置,一次運(yùn)行隨后銷毀 帶來(lái)的開(kāi)發(fā)便捷性是PHP能夠流行起來(lái)的原因之一。市面上絕大多數(shù)PHP項(xiàng)目使用的都是基于該種架構(gòu)的變體锣杂。
LNMP-with-Swoole 是 LNMP的一種變體脂倦,其在LNMP的基礎(chǔ)上引入了Swoole組件。
和PHP-fpm一樣蹲堂,Swoole有一套自己的進(jìn)程管理機(jī)制狼讨。但由于代碼變得高度常駐和編程思維需要從同步到異步的轉(zhuǎn)變贝淤,所以Swoole和傳統(tǒng)的基于PHP-fpm的Web框架親和度很低,即使是適配升級(jí)過(guò)的老式Web框架柒竞,目前在Swoole上運(yùn)行的表現(xiàn)往往并不好。
因此出現(xiàn)了這在這種折中方案播聪,并沒(méi)有直接將原有PHP代碼運(yùn)行在Swoole中朽基,而是使用Swoole搭建了一個(gè)服務(wù),系統(tǒng)通過(guò)接口與Swoole通信离陶,從而為Web項(xiàng)目補(bǔ)充了異步處理的能力稼虎。我稱呼這種同時(shí)使用PHP-fpm和Swoole的系統(tǒng)為 半Swoole應(yīng)用。因?yàn)榻尤牒?jiǎn)單招刨,所以是絕大多數(shù)現(xiàn)有項(xiàng)目?jī)?yōu)先考慮的Swoole接入方案霎俩。
LNMP-with-Swoole模型雖然引入了Swoole和異步處理能力,但是核心還是php-fpm沉眶,實(shí)際上還遠(yuǎn)遠(yuǎn)沒(méi)有發(fā)揮出Swoole的真正優(yōu)勢(shì)打却。
Swoole-HTTP-Server和LNMP-with-Swoole相比有巨大的變化,這種模型中充當(dāng)WebServer角色的構(gòu)件不僅僅有nginx,應(yīng)用本身也包含了一個(gè)內(nèi)建WebServer,不過(guò)由于Swoole Http Server不是專業(yè)的Http Server谎倔,對(duì)Http的處理不完善 柳击,因此仍然需要使用Nginx作為靜態(tài)資源服務(wù)器以及反代,Swoole Http Server僅僅處理PHP相關(guān)的Http流量片习。
一方面由于Swoole已經(jīng)包含了WebServer捌肴,不再需要實(shí)現(xiàn)cgi或者fast-cgi的通用協(xié)議去和WebServer通信蹬叭,另一方面Swoole有自己的進(jìn)程管理,因此PHP-fpm可以直接被去除了状知。對(duì)于PHP資源而言秽五,在這種模型中,Swoole Http Server的地位相當(dāng)于傳統(tǒng)模型中的nginx和PHP-fpm之和。
一次加載常駐內(nèi)存试幽,不同的請(qǐng)求間基本上復(fù)用了onRequest以外的所有流程筝蚕,使得每個(gè)請(qǐng)求的開(kāi)銷大大降低;異步IO的特性使得這種模型吞吐量遠(yuǎn)遠(yuǎn)高于傳統(tǒng)的LNMP模型铺坞。另外相對(duì)于獨(dú)立的Swoole服務(wù)起宽,內(nèi)嵌在Web系統(tǒng)中的Swoole使用更加的直接方便,支持更好济榨。
Swoft和Swoole的關(guān)系是什么?
- Swoole是一個(gè)異步引擎,核心是為PHP提供異步IO執(zhí)行的能力坯沪,同時(shí)提供一套異步編程可能會(huì)用到的工具集。
- Swoole-HTTP-Server是Swoole的一個(gè)組件擒滑,是SwooleServer中的一種腐晾,提供了一個(gè)適合Swoole直接運(yùn)行的HttpServer環(huán)境。
- Swoft一個(gè)現(xiàn)代的Web框架丐一,和Swoole親和性高藻糖,同時(shí)也是上面提到的Swoole-HTTP-Server模型模型的一個(gè)實(shí)踐。
Swoft管理著該Web模型中的Swoole,以及Swoole-Http-server库车,對(duì)開(kāi)發(fā)者屏蔽swoole的種種復(fù)雜操作細(xì)節(jié)巨柒,并作為一個(gè)Web框架向開(kāi)發(fā)者提供各種Web開(kāi)發(fā)需要用到的路由,MVC柠衍,數(shù)據(jù)庫(kù)訪問(wèn)等功能組件等洋满。
Swoft是如何使用Swoole的?
最核心的就是HttpServerr以及RpcServer
Http服務(wù)器
Swoft直接使用的是Swoole內(nèi)建的\Swoole\Http\Server
,它已經(jīng)處理好所有Http層面的所有東西珍坊,我們只需要關(guān)注應(yīng)用本身,我們來(lái)看一下Http服務(wù)幾個(gè)重要生命周期點(diǎn)牺勾。
swoole啟動(dòng)前
這個(gè)階段進(jìn)行的行為有幾個(gè)特征
1.基礎(chǔ)bootstrap行為:如必須的常量define,composer加載器引入阵漏,配置讀取驻民。
2.需要生成被所有worker/task進(jìn)程共享的程序全局期的對(duì)象,如Swoole\Lock
,Swoft\Memory\Table
的創(chuàng)建。
3.啟動(dòng)時(shí)履怯,所有進(jìn)程中合計(jì)只能執(zhí)行一次的操作:如前置Process的啟動(dòng)回还。
4.Bean容器基本初始化,以及項(xiàng)目啟動(dòng)流程需要的coreBean的加載虑乖。
這塊涉及東西比較雜懦趋,為控制篇幅后續(xù)用單獨(dú)文章介紹。
和Http服務(wù)關(guān)系最密切的進(jìn)程是Swoole中的Worker進(jìn)程(組)疹味,絕大部分業(yè)務(wù)處理都在該進(jìn)程中進(jìn)行仅叫。
對(duì)于每個(gè)Swoole事件帜篇,Swoft都提供了對(duì)應(yīng)的Swoole監(jiān)聽(tīng)器(對(duì)應(yīng)@SwooleListener注解)作為事件機(jī)制的封裝。要理解Swoft的HttpServer是如何在Swoole下運(yùn)行的我們重點(diǎn)需要關(guān)注下兩個(gè)在兩個(gè)Swoole事件swoole.workerStart
和swoole.onRequest
诫咱。
swoole.workerStart事件
workerStart事件在TaskWorker/Worker進(jìn)程啟動(dòng)時(shí)發(fā)生笙隙,每個(gè)TaskWorker/Worker進(jìn)程里都會(huì)執(zhí)行一次。
這是個(gè)關(guān)鍵節(jié)點(diǎn)坎缭,因?yàn)?code>swoole.workerStart回調(diào)之后新建的對(duì)象都是進(jìn)程全局期的竟痰,使用的內(nèi)存都屬于特定的Task/Worker進(jìn)程,相互獨(dú)立掏呼。也只有在這個(gè)階段或以后初始化的部分才是可以被熱重載的坏快。
事件底層關(guān)鍵代碼如下:
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點(diǎn)
- 初始化Bean容器:
上文中的BeanFactory::reload();
就是Swoft的Bean容器初始化入口,注解的掃描也是在此處進(jìn)行(實(shí)際上這個(gè)說(shuō)法并不準(zhǔn)確憎夷,Bean容器真正的初始化階段在Swoole Server啟動(dòng)前的BootStrap階段就已經(jīng)進(jìn)行了莽鸿,只不過(guò)那時(shí)進(jìn)行的是少部分初始化,相對(duì)swoole.workerStart
中的初始化的Bean數(shù)量拾给,比重很邢榈谩)。在workerStart
中初始化Bean容器是Swoft可以熱更新代碼的基礎(chǔ)蒋得。 - 初始化的應(yīng)用上下文
initApplicationContext->init()
會(huì)注冊(cè)Swoft事件監(jiān)聽(tīng)器(對(duì)應(yīng)@Listener),方便用戶處理Swoft應(yīng)用本身的各種鉤子级及。隨后觸發(fā)一個(gè)swoft.applicationLoader
事件,各組件通過(guò)該事件進(jìn)行配置文件加載,http/rpc路由注冊(cè)。 - 服務(wù)注冊(cè)
具體內(nèi)容會(huì)在服務(wù)治理章節(jié)講述额衙。
swoole.onRequest事件
每個(gè)http請(qǐng)求到來(lái)時(shí)僅僅會(huì)觸發(fā)swoole.onRequest
事件饮焦。
框架代碼本身都是由大量進(jìn)程全局期和少量程序全局期的對(duì)象構(gòu)成,而onReceive中創(chuàng)建的對(duì)象譬如$request
和$response
都是請(qǐng)求期的,隨著http請(qǐng)求的結(jié)束而回收入偷。
事件底層關(guān)鍵代碼如下:
/**
* 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)
:
設(shè)置請(qǐng)求上下文追驴,并觸發(fā)一個(gè)swoft.beforeRequest
事件械哟。 -
RequestHandler->handle($request)
:
執(zhí)行各個(gè) 中間件 和請(qǐng)求對(duì)應(yīng)的 action疏之,具體處理可以參考RPC章節(jié),原理基本相同暇咆。 -
$afterDispatch($response)
:
整理http響應(yīng)報(bào)文發(fā)送客戶端并觸發(fā)swoft.resourceRelease
(詳情在連接池一文中提及)事件和swoft.afterRequest
事件
總的來(lái)說(shuō)锋爪,縱觀這幾個(gè)生命周期點(diǎn)你需要搞清楚幾件事:
- Swoole的worker進(jìn)程是你絕大多數(shù)Http服務(wù)代碼的運(yùn)行環(huán)境。
- 一部分初始化和加載操作在swoole的server啟動(dòng)前完成爸业,一部分在
swoole.workerStart事件回調(diào)
中完成其骄,前者無(wú)法熱重載但可能被多個(gè)進(jìn)程共享。 - 初始化代碼只會(huì)在系統(tǒng)啟動(dòng)和Worker/Task進(jìn)程啟動(dòng)時(shí)執(zhí)行一次扯旷, 不像PHP-fpm每次請(qǐng)求都會(huì)執(zhí)行一次拯爽,框架對(duì)象也不像PHP-fpm會(huì)隨請(qǐng)求返回而銷毀。
- 每次請(qǐng)求都會(huì)觸發(fā)一次
swoole.onRequest事件
钧忽,里面就是我們的請(qǐng)求處理代碼真正運(yùn)行的地方毯炮,只有這事件內(nèi)產(chǎn)生的對(duì)象才會(huì)在請(qǐng)求結(jié)束時(shí)被回收逼肯。
RPC服務(wù)器
生命周期和Http服務(wù)基本一致,詳情參考《[原創(chuàng)]Swoft源碼剖析-RPC功能實(shí)現(xiàn)》
Swoft源碼剖析系列目錄:http://www.reibang.com/p/2f679e0b4d58