Swoft 源碼剖析 - Swoole和Swoft的那些事 (Http/Rpc服務篇)

前言

SwoftPHPer圈中是一個門檻較高的Web框架,不只僅因爲框架自己帶來了不少新概念和前沿的設計,還在於Swoft是一個基於Swoole的框架。SwoolePHPer圈內學習成本最高的工具沒有之一,雖然Swoft的出現下降了Swoole的使用成本,但若是你對Swoole自己瞭解不夠深刻,仍然很難避免栽進種種"坑"中。php

考慮到這個現狀,也爲下降閱讀難度,後續幾個和Swoole聯繫較爲密切的機制,筆者會調整寫做思路,將文章的定位從 「幫助讀者深刻理解Swoft」 調整爲 「幫助讀者理解Swoft和Swoole」,敘述節奏也會放慢。數據庫

三種PHP應用的Web模型

 

LNMPLAMP是絕大多數PHPer最熟悉的基礎Web架構,這裏以常見的LNMP做爲例子描述一個常見 無Swoole應用的構件組成:Nginx充當Web ServicePHP-FPM維護一個進程池去運行Web項目。編程

對比更古老的CGI模型,PHP-FPM已經引入了進程常駐的概念,避免每次請求建立並銷燬進程的開銷以及拓展加載的開銷,可是每一個請求仍然要執行PHP RINIT 與 RSHUTDOWN 之間的全部流程,包括從新加載一次框架源碼以及項目代碼,形成極大的性能浪費。bootstrap

這種模型的優勢是簡單成熟和穩定,一次運行隨後銷燬 帶來的開發便捷性是PHP可以流行起來的緣由之一。市面上絕大多數PHP項目使用的都是基於該種架構的變體。服務器

 

LNMP-with-Swoole 是 LNMP的一種變體,其在LNMP的基礎上引入了Swoole組件。
PHP-FPM同樣,Swoole有一套本身的進程管理機制。但因爲代碼變得高度常駐和編程思惟須要從同步到異步的轉變,因此Swoole和傳統的基於PHP-FPMWeb框架親和度很低,即便是適配升級過的老式Web框架,目前在Swoole上運行的表現每每並很差。swoole

所以出現了這在這種折中方案,並無直接將原有PHP代碼運行在Swoole中,而是使用Swoole搭建了一個服務,系統經過接口與Swoole通訊,從而爲Web項目補充了異步處理的能力。我稱呼這種同時使用PHP-FPMSwoole的系統爲 半Swoole應用。由於接入簡單,因此是絕大多數現有項目優先考慮的Swoole接入方案。架構

LNMP-with-Swoole模型雖然引入了Swoole和異步處理能力,可是核心仍是PHP-FPM,實際上還遠遠沒有發揮出Swoole的真正優點。app

 

 

Swoole-HTTP-ServerLNMP-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的地位至關於傳統模型中的NginxPHP-FPM之和。異步

一次加載常駐內存,不一樣的請求間基本上覆用了onRequest之外的全部流程,使得每一個請求的開銷大大下降;異步IO的特性使得這種模型吞吐量遠遠高於傳統的LNMP模型。另外相對於獨立的Swoole服務,內嵌在Web系統中的Swoole使用更加的直接方便,支持更好。

Swoft 和 Swoole 的關係是什麼 ?

  1. Swoole是一個異步引擎,核心是爲PHP提供異步IO執行的能力,同時提供一套異步編程可能會用到的工具集。
  2. Swoole-HTTP-ServerSwoole的一個組件,是SwooleServer中的一種,提供了一個適合Swoole直接運行的HttpServer環境。
  3. 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 啓動前

這個階段進行的行爲有幾個特徵

  1. 基礎bootstrap行爲:如必須的常量定義,Composer加載器引入,配置讀取等;
  2. 須要生成被全部Worker/Task進程共享的程序全局期的對象,如Swoole\Lock,Swoft\Memory\Table的建立;
  3. 啓動時,全部進程中合計只能執行一次的操做:如前置Process的啓動;
  4. Bean容器基本初始化,以及項目啓動流程須要的coreBean的加載。

這塊涉及東西比較雜,爲控制篇幅後續用單獨文章介紹。

Http服務關係最密切的進程是Swoole中的Worker進程(組),絕大部分業務處理都在該進程中進行。
對於每一個Swoole事件Swoft都提供了對應的Swoole監聽器(對應@SwooleListener註解)做爲事件機制的封裝。要理解SwoftHttpServer是如何在Swoole下運行的咱們重點須要關注下兩個在兩個Swoole事件swoole.workerStartswoole.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點

  1. 初始化Bean容器:
    上文中的BeanFactory::reload();就是SwoftBean容器初始化入口,註解的掃描也是在此處進行(實際上這個說法並不許確,Bean容器真正的初始化階段在Swoole Server啓動前的BootStrap階段就已經進行了,只不過那時進行的是少部分初始化,相對swoole.workerStart中的初始化的Bean數量,比重很小)。在workerStart中初始化Bean容器是Swoft能夠熱更新代碼的基礎。
  2. 初始化的應用上下文
    initApplicationContext->init()會註冊Swoft事件監聽器(對應@Listener),方便用戶處理Swoft應用自己的各類鉤子。隨後觸發一個swoft.applicationLoader事件,各組件經過該事件進行配置文件加載,HTTP/RPC路由註冊。
  3. 服務註冊
    具體內容會在服務治理章節講述。

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; }
  1. beforeDispatch($request, $response):
    設置請求上下文,並觸發一個swoft.beforeRequest事件。
  2. RequestHandler->handle($request):
    執行各個 中間件 和請求對應的 Action。
  3. $afterDispatch($response):
    整理HTTP響應報文發送客戶端並觸發swoft.resourceRelease事件和swoft.afterRequest事件

總的來講,縱觀這幾個生命週期點你須要搞清楚幾件事:

    1. SwooleWorker進程是你絕大多數HTTP服務代碼的運行環境。
    2. 一部分初始化和加載操做在SwooleServer啓動前完成,一部分在swoole.workerStart事件回調中完成,前者沒法熱重載但可能被多個進程共享。
    3. 初始化代碼只會在系統啓動和Worker/Task進程啓動時執行一次, 不像PHP-FPM每次請求都會執行一次,框架對象也不像PHP-FPM會隨請求返回而銷燬。
    4. 每次請求都會觸發一次swoole.onRequest事件,裏面就是咱們的請求處理代碼真正運行的地方,只有這事件內產生的對象纔會在請求結束時被回收。

      RPC服務器

      生命週期和HTTP服務基本一致

      以上是文章所有內容,有須要學習交流的友人請加入Swoole交流羣的我們一塊兒,有問題一塊兒交流,一塊兒進步!前提是你是學技術的。感謝閱讀!

      點此加入該羣

相關文章
相關標籤/搜索