Swoole高效跟傳統的web開發有什麼區別,除了傳統的LAMP/LNMP同步開發模式,swoole的異步開發模式是怎麼樣的。php
PHP web開發採用的方式是LAMP/LNMP架構,即Linux、Nginx,Mysql和PHP。這裏以nginx來舉例,大體結構爲:前端
當請求進入時,web server將請求轉交給PHP-FPM,PHP-FPM是一個進程池架構的FastCGI服務,內置PHP解釋器。FPM負責解釋執行PHP文件生成響應,最終返回給web server,展示至前端。PHP文件中實現了許多業務邏輯,包括Mysql和Nosql的訪問,調用第三方應用等等。mysql
這樣的結構php-fpm和nginx的配合已經運行得足夠好,可是因爲php-fpm自己是同步阻塞進程模型,在請求結束後釋放全部的資源(包括框架初始化建立的一系列對象),致使PHP進程「空轉」(建立<-->銷燬<-->建立)消耗大量的CPU資源,從而致使單機的吞吐能力有限。react
每次請求處理的過程都意味着一次PHP文件解析,環境設置等沒必要要的耗時操做PHP進程處理完即銷燬,沒法在PHP程序中使用鏈接池等技術實現性能優化。nginx
針對傳統架構的問題,swoole從PHP擴展出發,解決了上述問題,對於swoole的進程模型,咱們剛剛已經學過了。程序員
相比於傳統架構,Swoole進程模型最大的特色在於其多線程Reactor模式處理網絡請求,使得其能輕鬆應對大量鏈接。web
除此以外的優勢還包括:sql
全異步非阻塞,佔用資源開銷小,程序執行效率高數據庫
程序運行只解析加載一次PHP文件,避免每次請求的重複加載編程
一、更難上手。這要求開發人員對於多進程的運行模式有更清晰的認識
二、更容易內存泄露。在處理全局變量,靜態變量的時候必定要當心,這種不會被GC清理的變量會存在整個生命週期中,若是沒有正確的處理,很容易消耗完全部的內存。在php-fpm下,php代碼執行完內存就會被徹底釋放。
通常而言,在編程屆中註解是一種和註釋平行的概念,在解釋註解以前咱們須要先定義一下 註解 與 註釋 的區別:
註釋:給程序員看,幫助理解代碼,對代碼起到解釋、說明的做用。
註解:給應用程序看,註解每每充當着對代碼的聲明和配置的做用,爲可執行代碼提供機器可用的額外信息,在特定的環境下會影響程序的執行。
框架能夠基於這些元信息爲代碼提供各類額外功能,本質上註解就是理解註解只是配置的另外一種展示方式:
好比經過註解的方式實現權限的控制,就比配置文件當中配置要更加的方便
好比利用註解的方式配置路由、配置定時任務
現有的基於swoole的框架不少都是基於註解開發的,因此咱們須要對註解機制有了解,接下來利用代碼來實現下註解
3.一、什麼是容器?
容器 就是一個巨大的工廠,用於存放和管理 對象的生命週期,而且可以解決程序的依賴關係,實現解耦。
1 /** 2 * 耦合嚴重的寫法 3 **/ 4 class db { 5 public static function get_db() { 6 return new mysqli('127.0.0.1','user','pass','dbname',3306); 7 } 8 } 9 class post { 10 private $db; 11 public function __construct($db){ 12 //假設數據庫驅動發生了變化呢?若是寫死只能直接改動代碼 13 $this->db =new mysqli('127.0.0.1','user','pass','dbname',3306); } 14 public function get_post($id){ 15 return $this->db->query('SELECT * FROM post WHERE id ='.$id); 16 } 17 } 18 $post = new post(); 19 $post->get_post(12); 20 21 /* 22 *依賴注入的方式 23 */ 24 <?php 25 class db { 26 public static function get_db() { 27 return new mysqli('127.0.0.1','user','pass','dbname',3306); 28 } 29 } 30 class post { 31 private $db; 32 public function set_db(db $db){ 33 $this->db = $db; 34 } 35 public function get_post($id){ 36 return $this->db->query(select xx from xxx); 37 } 38 } 39 $post = new post(); 40 $post->set_db( db::get_db() ); //注入post類依賴的數據庫鏈接對象,經過類名直接調用靜態方法get_db 41 $post->get_post(11);
當沒有Ioc/DI容器時
當有了IoC/DI的容器後,post類再也不主動去建立db類了,以下圖所示:
依賴注入:在A類中使用了B類的實例時,B對象的構造不是在A類某個方法中初始化的,而是在A類外部初始化以後以B類的對象傳入進來。這個過程就是依賴注入。所須要的類經過參數的形式傳入的就是依賴注入。
依賴注入:在A類中使用了B類的實例時,B對象的構造不是在A類某個方法中初始化的,而是在A類外部初始化以後以B類的對象傳入進來。這個過程就是依賴注入。所須要的類經過參數的形式傳入的就是依賴注入。
控制反轉IoC(Inversion of Control)是說建立對象的控制權進行轉移,之前建立對象的主動權和建立時機是由本身把控的,而如今這種權力轉移到第三方,好比轉移交給了IOC容器,它就是一個專門用來建立對象的工廠,你要什麼對象,它就給你什麼對象,有了 IOC容器,依賴關係就變了,原先的依賴關係就沒了,它們都依賴IOC容器了,經過IOC容器來創建它們之間的關係,控制反轉意思是說將依賴類的控制權交出去,由主動變爲被動。
3.三、爲何說在swoole當中使用容器更有意義?
傳統的php框架沒有常駐內存,所以每次請求進來都須要把用到的類都實例化一次,每次實例化都須要申請內存,當請求處理完以後又須要釋放,具體請參看第一點,因此咱們能夠在server啓動的時候就把類實例化預先放到內存中,減入對象的建立時間。
一個簡單的bean容器
1 class BeanFactory{ 2 private static $container=[]; 3 4 public static function set(string $name,callable $func){ 5 self::$container[$name]=$func; 6 } 7 8 9 public static function get(string $name){ 10 if(isset(self::$container[$name])){ 11 return (self::$container[$name])(); 12 } 13 return null; 14 } 15 }
Swoole的高效不只僅於底層使用c編寫,他的進程結構模型也使其能夠高效的處理業務,咱們想要深刻學習,而且在實際的場景當中使用必須瞭解,下面咱們先看一下結構圖
首先先介紹下swoole的這幾種進程分別是幹什麼的
從這些層級的名字,咱們先大概說一下,下面這些層級分別是幹什麼的,作一個詳細的說明。
一、Master進程:主進程
二、Manger進程:管理進程
三、Worker進程:工做進程
四、Task進程:異步任務工做進程
第一層,Master進程,這個是swoole的主進程,這個進程是用於處理swoole的核心事件驅動的,那麼在這個進程當中能夠看到它擁有一個MainReactor[線程]以及若干個Reactor[線程],swoole全部對於事件的監聽都會在這些線程中實現,好比來自客戶端的鏈接,信號處理等。
每個線程都有本身的用途,下面多每一個線程有一個瞭解
主線程會負責監聽server socket,若是有新的鏈接accept,主線程會評估每一個Reactor線程的鏈接數量。將此鏈接分配給鏈接數最少的reactor線程,作一個負載均衡。
Reactor線程負責維護客戶端機器的TCP鏈接、處理網絡IO、收發數據徹底是異步非阻塞的模式。
swoole的主線程在Accept新的鏈接後,會將這個鏈接分配給一個固定的Reactor線程,在socket可讀時讀取數據,並進行協議解析,將請求投遞到Worker進程。在socket可寫時將數據發送給TCP客戶端。
Swoole配置了心跳檢測以後,心跳包線程會在固定時間內對全部以前在線的鏈接
發送檢測數據包
接收而且處理客戶端udp數據包
Swoole想要實現最好的性能必須建立出多個工做進程幫助處理任務,但Worker進程就必須fork操做,可是fork操做是不安全的,若是沒有管理會出現不少的殭屍進程,進而影響服務器性能,同時worker進程被誤殺或者因爲程序的緣由會異常退出,爲了保證服務的穩定性,須要從新建立worker進程。
Swoole在運行中會建立一個單獨的管理進程,全部的worker進程和task進程都是從管理進程Fork出來的。管理進程會監視全部子進程的退出事件,當worker進程發生致命錯誤或者運行生命週期結束時,管理進程會回收此進程,並建立新的進程。換句話也就是說,對於worker、task進程的建立、回收等操做全權有「保姆」Manager進程進行管理。
再來一張圖梳理下Manager進程和Worker/Task進程的關係。
worker 進程屬於swoole的主邏輯進程,用戶處理客戶端的一系列請求,接受由Reactor線程投遞的請求數據包,並執行PHP回調函數處理數據生成響應數據併發給Reactor線程,由Reactor線程發送給TCP客戶端能夠是異步非阻塞模式,也能夠是同步阻塞模式
taskWorker進程這一進城是swoole提供的異步工做進程,這些進程主要用於處理一些耗時較長的同步任務,在worker進程當中投遞過來。
一、client請求到達 Main Reactor,Client其實是與Master進程中的某個Reactor線程發生了鏈接。
二、Main Reactor根據Reactor的狀況,將請求註冊給對應的Reactor
三、客戶端有變化時Reactor將數據交給worker來處理
四、worker處理完畢,經過進程間通訊(好比管道、共享內存、消息隊列)發給對應的reactor。
五、reactor將響應結果發給相應的鏈接請求處理完成
示意圖:
一個更通俗的比喻,假設Server就是一個工廠,那Reactor就是銷售,接受客戶訂單。而Worker就是工人,當銷售接到訂單後,Worker去工做生產出客戶要的東西。而Task_Worker能夠理解爲行政人員,能夠幫助Worker幹些瑣事,讓Worker專心工做。
Master進程內的回調函數
onStart Server啓動在主進程的主線程回調此函數
onShutdown 此事件在Server正常結束時發生
Manager進程內的回調函數
onManagerStart 當管理進程啓動時調用它
onManagerStop 當管理進程結束時調用它
onWorkerError 當worker/task_worker進程發生異常後會在Manager進程內回調此函數
Worker進程內的回調函數
onWorkerStart 此事件在Worker進程/Task進程啓動時發生
onWorkerStop 此事件在worker進程終止時發生。
onConnect 有新的鏈接進入時,在worker進程中回調
onClose TCP客戶端鏈接關閉後,在worker進程中回調此函數
onReceive 接收到數據時回調此函數,發生在worker進程中
onRequest 有新的鏈接進入時,在worker進程中回調
onPacket 接收到UDP數據包時回調此函數,發生在worker進程中
onFinish 當worker進程投遞的任務在task_worker中完成時,task進程會經過finish()方法將任務處理的結果發送給worker進程。
onWorkerExit 僅在開啓reload_async特性後有效。異步重啓特性
onPipeMessage 當工做進程收到由 sendMessage 發送的管道消息時會觸發事件
Task進程內的回調函數
onTask 在task_worker進程內被調用。worker進程可使用swoole_server_task函數向task_worker進程投遞新的任務 onWorkerStart 此事件在Worker進程/Task進程啓動時發生 onPipeMessage 當工做進程收到由 sendMessage 發送的管道消息時會觸發事件
Swoole之因此性能卓越,是由於Swoole減小了每一次請求加載PHP文件以及初始化的開銷。可是這種優點也致使開發者沒法像過去同樣,修改PHP文件,從新請求,就能獲取到新代碼的運行結果。若是須要新代碼開始執行,每每須要先關閉服務器而後重啓,這樣才能使得新文件被加載進內存運行,這樣很明顯不能知足開發者的需求。幸運的是,Swoole提供了這樣的功能。
在swoole中,咱們能夠向主進程發送各類不一樣的信號,主進程根據接收到的信號類型作出不一樣的處理。好比下面這幾個
一、kill -SIGTERM master_pid 終止Swoole程序,一種優雅的終止信號,會待進程執行完當前程序以後中斷,而不是直接幹掉進程
二、kill -USR1 master_pid 重啓全部的Worker進程
三、kill -USR2|-12 master_pid 重啓全部的Task Worker進程
當USR1信號被髮送給Master進程後,Master進程會將一樣的信號經過Manager進程轉發Worker進程,收到此信號的Worker進程會在處理完正在執行的邏輯以後,釋放進程內存,關閉本身,而後由Manager進程重啓一個新的Worker進程。新的Worker進程會佔用新的內存空間,從新加載文件。
具體場景:
若是是上線的項目,一臺繁忙的後端服務器隨時都在處理請求,若是管理員經過kill進程方式來終止/重啓服務器程序,可能致使恰好代碼執行到一半終止。
這種狀況下會產生數據的不一致。如交易系統中,支付邏輯的下一段是發貨,假設在支付邏輯以後進程被終止了。會致使用戶支付了貨幣,但並無發貨,後果很是嚴重。
這個時候咱們須要考慮如何平滑重啓server的問題了。所謂的平滑重啓,也叫「熱重啓」,就是在不影響用戶的狀況下重啓服務,更新內存中已經加載的php程序代碼,從而達到對業務邏輯的更新。
swoole爲咱們提供了平滑重啓機制,咱們只須要向swoole_server的主進程發送特定的信號,便可完成對server的重啓。
注意事項:
一、更新僅僅只是針對worker進程,也就是寫在master進程跟manger進程當中更新代碼並不生效,也就是說只有在onWorkerStart回調以後加載的文件,重啓纔有意義。在Worker進程啓動以前就已經加載到內存中的文件,若是想讓它從新生效,只能關閉server再重啓。
二、直接寫在worker代碼當中的邏輯是不會生效的,就算髮送了信號也不會,須要經過include方式引入相關的業務邏輯代碼纔會生效
4、爲何須要分佈式服務
單體架構在規模比較小的狀況下工做狀況良好,可是隨着系統規模的擴大,它暴露出來的問題也愈來愈多,主要有如下幾點:
1.複雜性逐漸變高
好比有的項目有幾十萬行代碼,各個模塊之間區別比較模糊,邏輯比較混亂,代碼越多複雜性越高,越難解決遇到的問題。
2.技術債務逐漸上升
公司的人員流動是再正常不過的事情,有的員工在離職以前,疏於代碼質量的自我管束,致使留下來不少坑,因爲單體項目代碼量龐大的驚人,留下的坑很難被發覺,這就給新來的員工帶來很大的煩惱,人員流動越大所留下的坑越多,也就是所謂的技術債務愈來愈多。
3.阻礙技術創新
好比之前的某個項目使用tp3.2寫的,因爲各個模塊之間有着千絲萬縷的聯繫,代碼量大,邏輯不夠清楚,若是如今想用tp5來重構這個項目將是很是困難的,付出的成本將很是大,因此更多的時候公司不得不硬着頭皮繼續使用老的單體架構,這就阻礙了技術的創新。
4.沒法按需伸縮
好比說推薦模塊是CPU密集型的模塊,而訂單模塊是IO密集型的模塊,假如咱們要提高訂單模塊的性能,好比加大內存、增長硬盤,可是因爲全部的模塊都在一個架構下,所以咱們在擴展訂單模塊的性能時不得不考慮其它模塊的因素,由於咱們不能由於擴展某個模塊的性能而損害其它模塊的性能,從而沒法按需進行伸縮。
5.系統高可用性差
由於全部的功能開發最後都部署到同一個框架裏,運行在同一個進程之中,一旦某一功能涉及的代碼或者資源有問題,那就會影響整個框架中部署的功能。
RPC(Remote Procedure Call)—遠程過程調用,它是一種經過網絡從遠程計算機程序上請求服務,而不須要了解底層網絡技術的協議。
好比說兩臺服務器A,B,一個應用部署在A服務器上,想要調用B服務器上應用提供的函數/方法,因爲不在一個內存空間,不能直接調用,就須要經過網絡來表達調用的語義和傳達調用的數據,而這種方式就是rpc
5.一、爲何須要RPC?
RPC 的主要功能目標是讓構建分佈式計算(應用)更容易,在提供強大的遠程調用能力時不損失本地調用的語義簡潔性。爲實現該目標,RPC 框架需提供一種透明調用機制讓使用者沒必要顯式的區分本地調用和遠程調用。
Call(「listServices」)->info();
rpc隱藏了通信的細節,調用遠程的服務就像調用本地的代碼同樣,其調用協議一般包含傳輸協議和編碼協議。
傳輸協議: 能夠是自定義的tcp協議,能夠是http、websockect
編碼協議: 如基於文本編碼的 xml、 json,也有二進制編碼的 protobuf 、binpack 等。
5.二、使用什麼協議?
RPC是一個軟件結構概念,是構建分佈式應用的理論基礎。就比如爲啥你家能夠用到發電廠發出 來的電?
是由於電是能夠傳輸的。至於用銅線仍是用鐵絲仍是其餘種類的導線,也就是用http仍是用其餘協議的問題了。這個要看什麼場景,對性能要求怎麼樣。
5.三、rpc就只是接口調用?
一個完善的rpc其實還包含另外一塊內容,通訊協議外還有「服務註冊發現」,錯誤重試,服務限流,服務調用的負載均衡等等,rpc是不只僅是一套設計規範,還包含了服務治理。
傳輸協議: TCP協議
編碼協議: json編碼
個人知乎專欄