Swoole進程模型

進程

什麼是進程php

進程Process是計算機中的程序關於某數據集合上的一次運行活動,是系統分配資源和調度的基本單位,是操做系統結構的基礎。在早期面向進程設計的計算機結構中,進程是程序的基本執行實體。在當代面向線程設計的計算機結構中,進程是線程的容器。簡單來講,程序是指令、數據以及其組織形式的描述,而進程則是程序的實體。前端

在操做系統中,進程表示正在運行的程序,例如在終端中使用PHP命令運行PHP腳本,此時就至關於建立了一個進程,這個進程會在系統中駐存,申請屬於它本身的內存空間和系統資源,而且運行相應的程序。shell

$ php build.php
<?php //獲取當前進程的PID echo posix_getpid(); //修改所在進程的名稱 swoole_set_process_name("swoole process master"); //模擬持續運行100秒的程序 sleep(100);//持續運行100秒的目的是爲了在進程中能夠查看而不至於很快結束 

運行程序編程

$ php build.php
71

查看進程vim

$ ps aux | grep 71
root         1  0.0  0.1  18188  1712 pts/0    Ss+  11:07   0:00 /bin/bash
root        71  0.0  3.0 340468 30788 pts/2    S+   13:41   0:00 swoole process master
root        76  0.0  0.0  11112   940 pts/1    S+   13:42   0:00 grep 71

對於一個進程來講,最核心的內容可分爲兩部分:一部分是它的內存,這個內存是在建立初始時從系統中分配的,進程中全部建立的變量都會存儲在內存環境中。另外一部分是上下文環境, 進程是運行在操做系統中的,對於程序而言,它的運行依賴於操做系統分配的資源、操做系統的狀態以及程序自身的狀態,這些就構成了進程的上下文環境。數組

父子進程

 
父子進程
  • 子進程會複製父進程的內存空間和上下文環境
  • 子進程會複製父進程的IO句柄即fd描述符
  • 子進程的內存空間與父進程的內存空間是獨立,是互不影響的。
  • 修改子進程的內存空間並不會修改父進程或其餘子進程的內存空間

例如:父進程經過fopen打開文件後獲得一個IO句柄fd,子進程複製父進程後一樣會獲得這個fd。若是父進程和子進程同時對一個文件進行操做,會形成文件混亂,所以須要加互斥鎖。緩存

例如:父進程中的變量x=1,父進程派生子進程後,子進程也會存在變量x=1,可是修改父進程中的變量x並不會影響子進程的變量x的值。安全

多進程

PHP是單進程執行的,在處理高併發時主要依賴於Web服務器或PHP-FPM的多進程管理以及進程的複用,但在PHP實現多進程尤爲是後臺PHP-CLI模式下處理大量數據或運行後臺Deamon守護進程時,多進程的優點天然是最好的。性能優化

PHP的多線程也曾被人說起,但進程內多線程資源共享和分配問題難以解決,PHP有一個多線程過的擴展pthreads,它要求PHP環境必須是線程安全的。bash

多進程簡單來講就是多個進程同時執行多個任務,能夠將耗時但又必須執行的查詢分紅多個子進程進行操做。

  • PHP多進程不支持PHP-FPM和CGI模式,只能經過PHP-CLI模式。
  • PHP多進程適用於定時任務執行,互斥且耗時的任務。

開發使用PHP多進程的場景也就是使用PHP-FPM,PHP-FPM做爲PHP的多進程管理器,當使用Nginx做爲WebServer時,來自客戶端的請求會根據Nginx的路由配置,將以PHP爲後綴的文件轉發給PHP-FPM。當多個用戶同時請求API時,PHP-FPM會開啓多個PHP的處理進程進行處理。

檢查PHP是否支持多進程擴展

$ php -m | grep pcntl

多進程的優點

PHP相比C、C++、Java少了多線程,PHP中只有多進程的方案,因此PHP中的全局變量和對象不是共享的,數據結構也不能跨進程操做,另外Socket文件描述符也不能共享...

多線程看似比多進程強大的多,多線程的缺陷也一樣明顯:

  • 數據同步時,要麼犧牲性能處處加鎖,要麼使用地獄難度的無鎖併發編程。
  • 當程序邏輯複雜後,鎖會愈來愈難以控制。一旦死鎖,程序基本上就完了。
  • 某個線程掛掉後全部的線程都會退出

相比較多線程,多進程擁有的優點是

  • 配合進程間通訊,基本能夠實現任意數據共享。
  • 多進程不須要鎖
  • 多進程能夠共享內存的數據結構實現一些多線程的功能

對於併發服務器核心是IO,並不是大規模密集運算,高併發的服務器單機能維持10W鏈接,每秒能夠處理3~5W筆消息收發。

普通的Web應用都是IO密集型的程序,瓶頸在MySQL上,因此體現不出PHP的性能優點。但在密集計算方面比C/C++、Java等靜態編譯語言相差幾十倍甚至上百倍。

例如:使用多進程方式同時訪問Web地址

$ vim multi.php
<?php echo "process begin: ".date("Y-m-d H:i:s").PHP_EOL; //初始化地址數組 $urls = [ "http://www.baidu.com", "http://www.360.com", "http://www.qq.com", "http://www.sina.com" ]; //初始化數組用於回收線程管道內容 $workers = []; //按照任務分配線程 for($i=0; $i<count($urls); $i++){ $url = $urls[$i]; //建立進程 $process = new swoole_process(function(swoole_process $worker) use($url){ //模擬執行耗時任務 file_get_contents($url); //sleep(1);//模擬耗時1秒 echo $url.PHP_EOL; }, true); //開啓進程 $pid = $process->start(); $workers[$pid] = $process; } //打印管道內容 foreach($workers as $worker){ echo "pid : ".$worker->read(); } echo "process end: ".date("Y-m-d H:i:s").PHP_EOL; 

運行代碼

$ php multi.php
process begin: 2019-06-22 16:19:48
pid : http://www.baidu.com
pid : http://www.360.com
pid : http://www.qq.com
pid : http://www.sina.com
process end: 2019-06-22 16:19:49

內存共享

進程之間是相互獨立的,那麼如何實現進程之間的通訊呢? 這裏可使用共享內存的方式來實現。

共享內存ShareMemory是映射一段能被其餘進程所訪問的內存,這段共享內存由一個進程建立,但多個進程均可以訪問。共享內存是最快的IPC方式,是針對其餘進程之間通訊效率低下而專門設計的,它每每與其它通訊機制,如信號量配置使用以實現進程之間的同步和通訊。

共享內存是操做系統中比較特殊的內存,它並不依賴於任何進程, 也不屬於任何進程。經過調用系統函數建立共享內存,並指定它的索引,也就是它的IDshmid,經過索引任何進程均可以在共享內存中申請內存空間並存儲對應的值。

 
共享內存
  • 共享內存並不屬於任何一個進程
  • 在共享內存中分配的內存空間能夠被任何進程訪問
  • 即便進程關閉,共享內存仍然能夠繼續保存在操做系統中。

查看操做系統中共享內存的分片

$ ipcs -m
------------ 共享內存段 --------------
鍵        shmid      擁有者  權限     字節     鏈接數  狀態      
0x00000000 131072     jc         777        16384      1          目標       
0x00000000 327681     jc         600        67108864   2          目標       
0x00000000 262146     jc         777        8077312    2          目標

Swoole沒有采用多線程模型而使用了多線程模型,在必定程度上減小了訪問數據時加鎖解鎖的開銷,但同時也引入了新的需求 共享內存。Swoole中爲了更好的進行內存管理,減小頻繁分配釋放內存空間形成的損耗和內存碎片,Rango實際並實現了三種不一樣功能的內存池分別時FixedPoolRingBufferMemoryGlobal

Swoole開發模式

對於傳統PHP的Web開發而言,最經常使用的是LNMP架構。在LNMP架構中,當請求進入時,WebServer會將請求轉交給PHP-FPM,PHP-FPM是一個進程池架構的FastCGI服務,內置了PHP解釋器。PHP-FPM負責解釋執行PHP文件並生成響應,最終返回給WebServer展示至前端。因爲PHP-FPM自己是同步阻塞進程模型,在請求結束後會釋放掉全部資源,包括框架初始化建立的一些列對象,從而致使PHP進程進入「空轉」消耗大量CPU資源,最終致使單機的吞吐能力有限。

另外,在每次請求處理的過程都意味着一次PHP文件解析、環境設置等沒必要要的耗時操做,當PHP進程處理完後就會銷燬,沒法在PHP程序中使用鏈接池等技術實現性能優化。

針對傳統架構的問題,Swoole從PHP擴展下手,解決了上述問題。相比較傳統的Web架構,Swoole進程模型最大的特色在於多線程Reactor模式處理網絡請求,使其能輕鬆應對大量鏈接。

除此以外,Swoole是全異步非阻塞,所以佔用資源少,程序執行效率高。在Swoole中程序運行只解析加載一次PHP文件,避免每次請求的重複加載。再者,Swoole進程常駐,使得鏈接池和請求之間的信息傳遞的實現成爲可能。

使用Swoole開發時,須要開發人員對多進程的運行模式有着清晰的認識。另外,Swoole很容易形成內存泄露。在處理全局變量、靜態變量的時候要當心,這種不會被GC清理的變量會存在整個生命週期中。若是沒有正確的處理,很容易消耗完內存。而在PHP-FPM下,PHP代碼執行完畢內存就會被徹底釋放掉。

Swoole進程結構

LNMP架構中PHP是須要依賴Nginx這樣的Web服務器以及PHP-FPM這樣的多進程的PHP解析器。當一個請求到來時PHP-FPM會去建立一個新的進程去處理這個請求,在這種狀況下,系統的開銷很大程序上都用在建立和銷燬進程上,致使了程序的響應效率並非很是高。

Swoole的強大之處在於進程模型的設計,即解決了異步問題,又解決了併發問題。

Swoole的進程可分爲四種角色

  • Master進程
    保證Swoole機制運行,同時利用它建立Master主線程(負責接收鏈接、定時器等)和Reactor線程(處理鏈接並將請求分發給各個Worker進程)。
  • Manager進程
    Worker進程和Task進程均由Manager進程派生,Manager管理進程負責結束時回收子進程,避免殭屍進程的存在。
  • Worker進程
    用PHP回調函數處理由Reactor分發過來的請求數據,並生成響應數據發送給Reactor,由Reactor發送給TCP客戶端。
  • Task進程
    接收由Worker進程分發給它的任務,以多進程方式運行,處理好後將結果返回給它的Worker進程。
 
Swoole進程

Swoole中採用了和PHP-FPM徹底不一樣的架構,整個Swoole擴展能夠分爲三層:

 
Swoole進程模型

第1層:Master主進程

 
Master主進程

Master進程是Swoole的主進程,主要用於處理Swoole的核心事件驅動。Master主進程是一個多線程模型,擁有多個獨立的Reactor線程。

Master主進程包含Master線程、Reactor線程、心跳檢測線程、UDP收包線程。每一個Reactor子線程中都運行着一個epoll函數的實例,Swoole對於事件的監聽都會在Reactor線程中實現,好比來自客戶端的鏈接、本地通訊使用的管道、異步操做使用的文件以及文件描述符都會註冊在epoll函數中。

Master主進程使用select/poll進行IO事件循環,Master主進程中的文件描述符只有幾個,Reactor線程使用epoll,由於Reactor線程中會監聽大量鏈接的可讀事件,使用epoll能夠支持大量的文件描述符。

HTTP服務器爲例,Master主進程負責監聽端口,而後接收新的鏈接,並將這個鏈接分配給一個Reactor線程,由這個Reactor線程監聽此鏈接,一旦此鏈接可讀時,它會讀取數據並解析協議,而後將請求投遞到Worker工做進程中去執行。

Master主進程內的回調函數

  • onStart 服務器啓動時主進程的主線程回調此函數
  • onShutdown 服務器正常結束時發生

Master 線程

Swoole啓動後Master主線程會負責監聽服務器的socket,若是有新的鏈接accept,Master主線程會評估每一個Reactor線程的鏈接數量,並將此鏈接分配給鏈接最少的Reactor線程。這樣作的好處是:

  • 每一個Reactor線程持有的鏈接數很是均衡,沒有單個線程負載太高的問題。
  • 解決了驚羣問題,尤爲是擁有多個listen socket時,節約了線程喚醒和切換的開銷。
  • 主線程接管了全部信號signal的處理,使Reactor線程運行中能夠不被信號打斷。

主線程Master在accept新的鏈接後,會將這個鏈接分配給一個固定的Reactor線程,並由這個線程負責監聽此socket,在socket可讀時讀取數據,並進行協議解析,最後將請求投遞到Worker進程。

 
Master主線程

Reactor線程

  • 負責維護客戶端TCP鏈接、處理網絡IO、處理協議、收發數據。
  • 徹底是異步非阻塞的模式
  • 所有都是C代碼,除了Start/Shutdown事件回調外,不執行任何PHP代碼。
  • TCP客戶端發送來的數據緩衝、拼接、拆分爲完整的請求數據包。
  • Reactor以多線程的方式運行

Swoole擁有多線程Reactor,因此能夠充分利用多核,開啓CPU親和設置後,Reactor線程能夠綁定單獨的核,節省CPU Cache開銷。

Reactor線程負責處理TCP鏈接,是收發數據的線程。Swoole的Master主線程在accept新的鏈接後,會將這個鏈接分配給一個固定的Reactor線程,並由這個線程負責監聽此socket。在socket可讀時讀取數據,並進行協議解析,將請求投遞到Worker工做進程。在socket可寫時,將數據發送給TCP客戶端。

Reactor線程負責維護客戶端TCP鏈接、處理網絡IO、處理協議、收發數據,它徹底是異步非阻塞的模式。
Reactor線程是全異步非阻塞的,即便Worker進程採用了同步模式,依然不響應Reactor線程的性能。在Worker進程組很繁忙的狀態下,Reactor線程徹底不受影響,依然能夠收發處理數據。

因爲TCP是流式的沒有邊界,因此處理起來很麻煩。Reactor線程可使用EOF或者包頭長度,自動緩存數據、組裝數據包,等一個請求徹底收到後,再次遞交給Worker。

Reactor所有是C代碼,除了Start/Shutdown事件回調外,不執行任何PHP代碼。它將TCP客戶端發來的數據緩衝、拼接、拆分紅完整的一個請求數據包。

綜上所述,Master主進程中包含兩個關鍵線程:Master主線程和Reactor線程,Master主線程用來處理accept()事件,建立新的socket fd,當它接收到新鏈接後會將新的socket鏈接放到Reactor線程的事件監聽循環中,Reactor線程負責接收從客戶端發送過來的數據,並按協議解析後經過管道pipe傳遞給Worker工做進程進行處理。Worker工做進程處理完畢後,會將結果經過管道pipe回傳給Reactor線程,Reactor線程再按照協議將結果經過socket發送給客戶端。能夠看到Reactor線程負責數據的IO和傳輸,在Linux系統下這些IO事件都是經過epoll機制來處理的。

心跳包檢測線程HeartbeatCheck

Swoole配置了心跳檢測後心跳包線程會在固定事件內對全部以前在線的鏈接發送檢測數據包。

UDP收包線程UdpRecv

接收並處理客戶端UDP數據包

第2層:Manager管理進程

Swoole運行中會建立一個單獨的管理進程,全部的Worker進程和Task進程都是從管理進程fork建立出來的。

Manager管理進程會監聽全部子進程的退出事件,當Worker進程發生致命錯誤或運行生命週期結束時,Manager管理進程會回收此進程並建立新的進程。

Manager管理進程還能夠平滑地重啓全部工做進程Worker,以實現程序代碼的從新加載。

Manager管理進程管理着Worker工做進程或Task任務進程,Worker工做進程或Task任務進程都被Manager管理進程fork建立並管理着。

Manager進程負責建立和管理下層的Worker進程組和Task進程組,Manager進程中不會運行任何用戶層面的業務邏輯,僅僅只作進程的管理和分配。

Manager進程會fork建立出指定數量的Worker進程和Task進程。

Manager進程的工做職責

  • Worker工做進程和Task任務進程都是由Manager管理進程fork建立並管理的
  • 子進程結束運行時,Manager管理進程負責回收子進程,以免成爲殭屍進程,並建立新的子進程。
  • 服務器關閉時,Manager管理進程發送信號給全部子進程,並通知子進程關閉服務。
  • 服務器重啓時,Manager管理進程會逐個關閉或重啓子進程。

Manager進程內的回調函數

  • onManagerStart 當管理進程啓動時調用
  • onManagerStop 當管理進程結束時調用
  • onWorkerError 當Worker進程或Task進程發生異常後會在Manager進程會回調此函數

第3層:工做進程

  • 工做進程主要用於處理客戶端請求
  • 工做進程接收由Reactor線程投遞的請求數據包,並執行PHP回調函數處理數據。
  • 工做進程生成響應數據併發送給Reactor線程,由Reactor線程發送給TCP客戶端。
  • 工做進程能夠是異步非阻塞模式也能夠是同步阻塞模式。
  • 工做進程以多進程的方式運行

與傳統的半同步半異步服務器不一樣是,Swoole的工做進程能夠同步的也能夠異步的。這樣帶來了工做進程相似於PHP-FPM進程,它接收由Reactor線程投遞的請求數據包,並執行PHP回調函數處理數據。工做線程生成響應數據併發送給Reactor線程,由Reactor線程發送給TCP客戶端。工做線程能夠是異步模式,也能夠是同步模式。另外,工做線程以多進程的方式運行。

Swoole想要實現最好的性能就必須建立出多個工做進程幫助處理任務,可是工做進程必須fork操做,而fork操做又是不安全的。若是沒有管理將會出現不少殭屍進程,進而影響服務器性能。同時工做進程被誤殺或因爲程序緣由會引發異常退出,爲了保證服務的穩定性,須要從新建立工做進程。

工做進程可分爲兩類:Worker進程和Task進程

  • Worker進程是Swoole的主邏輯進程,用於處理來自客戶端的請求。

  • Task進程是Swoole提供的異步工做進程,用於處理耗時較長的同步任務。

Worker工做進程

Worker工做進程接收Reactor線程投遞過來的數據,執行PHP代碼,而後生成數據並交給Reactor線程,由Reactor線程經過TCP將數據返回給客戶端。若是是UDP,Worker工做進程會直接將數據發送給客戶端。

Worker進程中執行的PHP代碼,它等同於PHP-FPMPHP-FPM在處理異步操做時是很無力的,但Swoole提供的Task進程能夠很好的解決這個問題。Worker進程能夠將一些異步任務投遞給Task進程,而後直接返回,處理其餘由Reactor線程投遞過來的事件。

Worker進程內的回調函數

  • onWorkerStart 當Worker工做進程或Task任務進程啓動時觸發
  • onWorkerStop 當Worker進程終止時觸發
  • onConnect 當有新的鏈接進入時觸發
  • onClose 當TCP客戶端鏈接關閉後觸發
  • onReceive 當接收到數據時觸發
  • onPacket 當接收到UDP數據包是時觸發
  • onFinish 當Worker工做進程投遞的任務在task_worker中完成時,Task進程會經過finish()方法將任務處理的結果發送給Worker進程。
  • onWorkerExit 當開啓reload_async特性後有效,即異步重啓特性。
  • onPipeMessage 當工做進程收到由sendMessage發送的管道消息時觸發

Task任務進程

  • 異步工做進程
  • 接收由Worker工做進程經過swoole_server->taskswoole_server->taskwait方法投遞的任務。
  • 處理任務,並將結果數據返回給Worker工做進程swoole_server->finish
  • 同步阻塞模式
  • 以多進程的方式運行

Swoolen除了Reactor線程,Task任務工做進程是以異步的方式處理其它任務的進程,使用方式相似於Gearman。它接收由Worker進程經過swoole_server->task/taskwait方法投遞的任務,而後處理任務,並將結果數據使用swoole_server->finish返回給Worker進程。Task以多進程的方式進行運行。

簡單來講,能夠將Reactor理解爲Nginx,將Worker理解爲PHP-FPM。Reactor線程異步並行地處理網絡請求,而後再轉發給Worker工做進程中去處理(在回調函數中處理)。Reactor和Worker之間經過UnixSocket進行通訊。

Swoole除了Reactor線程,Worker工做進程還提供了Task進程池。目的是爲了解決業務代碼中,有些邏輯部分不須要立刻執行。利用Task進程池,能夠方便的投遞一個異步任務區執行。

Task進程以徹底同步阻塞的方式運行,一個Task進程在執行任務期間是不接受從Worker進程投遞的任務的,當Task進程執行完任務後,會異步的通知Worker進程並告訴它任務已經完成。

Task進程內的回調函數

  • onTask 在Task線程內被調用,Worker進程可以使用swoole_server_task函數向Task進程投遞新的任務。
  • onWorkerStart 在Worker或Task進程啓動時觸發
  • onPipeMessage 當工做進程收到由sendMessage發送的管道消息時觸發
 
Swoole的進程

Swoole進程協做

  1. 當客戶端主動連入服務器的時候,客戶端其實是與Master主進程中的某個Reactor線程發生了鏈接。
  2. 當TCP三次握手成功後,由這個Reactor線程將鏈接成功的消息告知Manager管理進程,再由Manager管理進程轉交給Worker工做進程,最終在Worker工做進程中觸發onConnect事件對應的方法。
  3. 當客戶端向服務器發送一個數據包的時候,首先接收到數據包的是Reactor線程,同時Reactor線程會完成組包,再將組裝好的包交給Manager管理進程,由Manager管理進程轉交給Worker工做進程,此時Worker工做進程觸發onReceive事件。
  4. 若是Worker工做進程中作了處理操做後再使用Send方法將數據發回給客戶端時,數據會沿着這個路徑逆流而上。
  5. Task任務進程用來處理一些佔用時間較長的業務,主要處理Worker工做進程中佔用時間較長的任務。

形象來講

  • Master主進程 = 業務窗口
  • Reactor線程 = 前臺接待員
  • Manager管理進程 = 項目經理
  • Worker工做進程 = 工人

當在業務窗口辦理業務時,若是用戶不少,後邊的用戶須要排隊等待服務,Reactor負責與客戶直接溝通,對客戶的請求進行初步的整理(傳輸層級別的整理,組包),而後Manager負責將業務分配給合適的Worker,如空閒的Worker,最終Worker負責實現具體的業務。

Swoole進程關係

Reactor和Worker與Task的關係,簡單理解可認爲:Reactor = Nginx、Worker = PHP-FPM

Reactor線程異步並行地處理網絡請求,而後再轉發給Worker工做進程中去處理。

Reactor線程和Worker工做進程之間經過socket進行通訊。

在PHP-FPM的應用中,常常會將一個任務異步投遞到Redis等隊列中,並在後臺啓動一些PHP進程異步地處理這些任務。

Swoole提供的Worker工做進程是一套更加完整的方案,它將任務投遞、隊列、PHP任務進程管理融爲一體。經過底層的API實現異步任務的處理。另外,Task任務進程能夠在任務執行完畢後,再返回一個結果反饋到Worker工做進程。

Swoole的Reactor、Worker、Task之間能夠緊密的結合起來,提供更加高級的使用方式。

假設Server是一個工廠

  • Reactor:銷售,接收客戶訂單。
  • Worker:工人,當銷售接單後,Worker去工做生產出客戶須要的東西。
  • Task:行政人員,幫助Worker幹些瑣事兒,讓Worker專心工做。

底層會爲Worker工做進程、Task任務進程分配一個惟一的ID,不一樣的Worker和Task任務進程之間能夠經過sendMessage接口進行通訊。

Swoole執行流程

 
Swoole執行流程
  1. 當客戶端請求進入Master主進程後會被Master主線程接收到
  2. 將讀寫操做的監聽註冊到對應的Reactor線程中,並通知Worker工做進程處理onConnect,也就是接收到鏈接的回調。
  3. 客戶端的數據會通知對應的Reactor線程併發送給Worker工做進程進行處理。
  4. 若是Worker工做進程投遞任務,將數據經過管道發送給Task任務進程,Task任務進程處理完後會發送給Worker工做進程。
  5. Worker工做進程會通知Reactor線程發送數據給客戶端。
  6. 當Worker工做進程出現異常時關閉,Manager管理進程會從新建立一個Worker工做進程,保證Worker工做進程的數量是固定的。
 
Swoole執行流程
相關文章
相關標籤/搜索