目錄
- 一、swoole的運行模式
- 1.1、傳統(tǒng)web開發(fā)模式
- 1.2、Swoole運行模式
- 1.3、使用swoole和傳統(tǒng)php開發(fā)的缺點
- 二、注解機制
- 三、容器
- 3.1、什么是容器?
- 3.2簡單的通過代碼理解依賴注入
- 3.3、為什么說在swoole當中使用容器更有意義?
- 3.4、Swoole進程結構
- 3.5、swoole運行模式及熱重啟
- 四、為什么需要分布式服務
- 五、什么是RPC?
- 5.1、為什么需要RPC?
- 5.2、使用什么協(xié)議?
- 5.3、rpc就只是接口調(diào)用?
- 5.4 實際操作
一、swoole的運行模式
1.1、傳統(tǒng)web開發(fā)模式
PHP web開發(fā)采用的方式是LAMP/LNMP架構,即Linux、Nginx,Mysql和PHP。這里以nginx來舉例,大致結構為:
當請求進入時,web server將請求轉(zhuǎn)交給PHP-FPM,PHP-FPM是一個進程池架構的FastCGI服務,內(nèi)置PHP解釋器。FPM負責解釋執(zhí)行PHP文件生成響應,最終返回給web server,展現(xiàn)至前端。PHP文件中實現(xiàn)了許多業(yè)務邏輯,包括Mysql和Nosql的訪問,調(diào)用第三方應用等等。
這樣的結構php-fpm和nginx的配合已經(jīng)運行得足夠好,但是由于php-fpm本身是同步阻塞進程模型,在請求結束后釋放所有的資源(包括框架初始化創(chuàng)建的一系列對象),導致PHP進程“空轉(zhuǎn)”(創(chuàng)建-->銷毀-->創(chuàng)建)消耗大量的CPU資源,從而導致單機的吞吐能力有限。
每次請求處理的過程都意味著一次PHP文件解析,環(huán)境設置等不必要的耗時操作PHP進程處理完即銷毀,無法在PHP程序中使用連接池等技術實現(xiàn)性能優(yōu)化。
1.2、Swoole運行模式
針對傳統(tǒng)架構的問題,swoole從PHP擴展出發(fā),解決了上述問題,對于swoole的進程模型,我們剛剛已經(jīng)學過了。
相比于傳統(tǒng)架構,Swoole進程模型最大的特點在于其多線程Reactor模式處理網(wǎng)絡請求,使得其能輕松應對大量連接。
除此之外的優(yōu)點還包括:
全異步非阻塞,占用資源開銷小,程序執(zhí)行效率高
程序運行只解析加載一次PHP文件,避免每次請求的重復加載
1.3、使用swoole和傳統(tǒng)php開發(fā)的缺點
1、更難上手。這要求開發(fā)人員對于多進程的運行模式有更清晰的認識
2、更容易內(nèi)存泄露。在處理全局變量,靜態(tài)變量的時候一定要小心,這種不會被GC清理的變量會存在整個生命周期中,如果沒有正確的處理,很容易消耗完所有的內(nèi)存。在php-fpm下,php代碼執(zhí)行完內(nèi)存就會被完全釋放。
二、注解機制
一般而言,在編程屆中注解是一種和注釋平行的概念,在解釋注解之前我們需要先定義一下 注解 與 注釋 的區(qū)別:
注釋:給程序員看,幫助理解代碼,對代碼起到解釋、說明的作用。
注解:給應用程序看,注解往往充當著對代碼的聲明和配置的作用,為可執(zhí)行代碼提供機器可用的額外信息,在特定的環(huán)境下會影響程序的執(zhí)行。
框架可以基于這些元信息為代碼提供各種額外功能,本質(zhì)上注解就是理解注解只是配置的另一種展現(xiàn)方式:
比如通過注解的方式實現(xiàn)權限的控制,就比配置文件當中配置要更加的方便
比如利用注解的方式配置路由、配置定時任務
現(xiàn)有的基于swoole的框架很多都是基于注解開發(fā)的,所以我們需要對注解機制有了解,接下來利用代碼來實現(xiàn)下注解
三、容器
3.1、什么是容器?
容器 就是一個巨大的工廠,用于存放和管理 對象的生命周期,并且能夠解決程序的依賴關系,實現(xiàn)解耦。
3.2簡單的通過代碼理解依賴注入
/**
* 耦合嚴重的寫法
**/
class db {
public static function get_db() {
return new mysqli('127.0.0.1','user','pass','dbname',3306);
}
}
class post {
private $db;
public function __construct($db){
//假設數(shù)據(jù)庫驅(qū)動發(fā)生了變化呢?如果寫死只能直接改動代碼
$this->db =new mysqli('127.0.0.1','user','pass','dbname',3306); }
public function get_post($id){
return $this->db->query('SELECT * FROM post WHERE id ='.$id);
}
}
$post = new post();
$post->get_post(12);
/*
*依賴注入的方式
*/
?php
class db {
public static function get_db() {
return new mysqli('127.0.0.1','user','pass','dbname',3306);
}
}
class post {
private $db;
public function set_db(db $db){
$this->db = $db;
}
public function get_post($id){
return $this->db->query(select xx from xxx);
}
}
$post = new post();
$post->set_db( db::get_db() ); //注入post類依賴的數(shù)據(jù)庫連接對象,通過類名直接調(diào)用靜態(tài)方法get_db
$post->get_post(11);
當沒有Ioc/DI容器時
當有了IoC/DI的容器后,post類不再主動去創(chuàng)建db類了,如下圖所示:
依賴注入:在A類中使用了B類的實例時,B對象的構造不是在A類某個方法中初始化的,而是在A類外部初始化之后以B類的對象傳入進來。這個過程就是依賴注入。所需要的類通過參數(shù)的形式傳入的就是依賴注入。
依賴注入:在A類中使用了B類的實例時,B對象的構造不是在A類某個方法中初始化的,而是在A類外部初始化之后以B類的對象傳入進來。這個過程就是依賴注入。所需要的類通過參數(shù)的形式傳入的就是依賴注入。
控制反轉(zhuǎn)IoC(Inversion of Control)是說創(chuàng)建對象的控制權進行轉(zhuǎn)移,以前創(chuàng)建對象的主動權和創(chuàng)建時機是由自己把控的,而現(xiàn)在這種權力轉(zhuǎn)移到第三方,比如轉(zhuǎn)移交給了IOC容器,它就是一個專門用來創(chuàng)建對象的工廠,你要什么對象,它就給你什么對象,有了 IOC容器,依賴關系就變了,原先的依賴關系就沒了,它們都依賴IOC容器了,通過IOC容器來建立它們之間的關系,控制反轉(zhuǎn)意思是說將依賴類的控制權交出去,由主動變?yōu)楸粍印?/p>
3.3、為什么說在swoole當中使用容器更有意義?
傳統(tǒng)的php框架沒有常駐內(nèi)存,因此每次請求進來都需要把用到的類都實例化一次,每次實例化都需要申請內(nèi)存,當請求處理完之后又需要釋放,具體請參看第一點,所以我們可以在server啟動的時候就把類實例化預先放到內(nèi)存中,減入對象的創(chuàng)建時間。
一個簡單的bean容器
class BeanFactory{
private static $container=[];
public static function set(string $name,callable $func){
self::$container[$name]=$func;
}
public static function get(string $name){
if(isset(self::$container[$name])){
return (self::$container[$name])();
}
return null;
}
}
3.4、Swoole進程結構
Swoole的高效不僅僅于底層使用c編寫,他的進程結構模型也使其可以高效的處理業(yè)務,我們想要深入學習,并且在實際的場景當中使用必須了解,下面我們先看一下結構圖
首先先介紹下swoole的這幾種進程分別是干什么的
從這些層級的名字,我們先大概說一下,下面這些層級分別是干什么的,做一個詳細的說明。
1、Master進程:主進程
2、Manger進程:管理進程
3、Worker進程:工作進程
4、Task進程:異步任務工作進程
Master進程
第一層,Master進程,這個是swoole的主進程,這個進程是用于處理swoole的核心事件驅(qū)動的,那么在這個進程當中可以看到它擁有一個MainReactor[線程]以及若干個Reactor[線程],swoole所有對于事件的監(jiān)聽都會在這些線程中實現(xiàn),比如來自客戶端的連接,信號處理等。
每一個線程都有自己的用途,下面多每個線程有一個了解
MainReactor(主線程)
主線程會負責監(jiān)聽server socket,如果有新的連接accept,主線程會評估每個Reactor線程的連接數(shù)量。將此連接分配給連接數(shù)最少的reactor線程,做一個負載均衡。
Reactor線程組
Reactor線程負責維護客戶端機器的TCP連接、處理網(wǎng)絡IO、收發(fā)數(shù)據(jù)完全是異步非阻塞的模式。
swoole的主線程在Accept新的連接后,會將這個連接分配給一個固定的Reactor線程,在socket可讀時讀取數(shù)據(jù),并進行協(xié)議解析,將請求投遞到Worker進程。在socket可寫時將數(shù)據(jù)發(fā)送給TCP客戶端。
心跳包檢測線程(HeartbeatCheck)
Swoole配置了心跳檢測之后,心跳包線程會在固定時間內(nèi)對所有之前在線的連接
發(fā)送檢測數(shù)據(jù)包
UDP收包線程(UdpRecv)
接收并且處理客戶端udp數(shù)據(jù)包
管理進程Manager
Swoole想要實現(xiàn)最好的性能必須創(chuàng)建出多個工作進程幫助處理任務,但Worker進程就必須fork操作,但是fork操作是不安全的,如果沒有管理會出現(xiàn)很多的僵尸進程,進而影響服務器性能,同時worker進程被誤殺或者由于程序的原因會異常退出,為了保證服務的穩(wěn)定性,需要重新創(chuàng)建worker進程。
Swoole在運行中會創(chuàng)建一個單獨的管理進程,所有的worker進程和task進程都是從管理進程Fork出來的。管理進程會監(jiān)視所有子進程的退出事件,當worker進程發(fā)生致命錯誤或者運行生命周期結束時,管理進程會回收此進程,并創(chuàng)建新的進程。換句話也就是說,對于worker、task進程的創(chuàng)建、回收等操作全權有“保姆”Manager進程進行管理。
再來一張圖梳理下Manager進程和Worker/Task進程的關系。
Worker進程
worker 進程屬于swoole的主邏輯進程,用戶處理客戶端的一系列請求,接受由Reactor線程投遞的請求數(shù)據(jù)包,并執(zhí)行PHP回調(diào)函數(shù)處理數(shù)據(jù)生成響應數(shù)據(jù)并發(fā)給Reactor線程,由Reactor線程發(fā)送給TCP客戶端可以是異步非阻塞模式,也可以是同步阻塞模式
Task進程
taskWorker進程這一進城是swoole提供的異步工作進程,這些進程主要用于處理一些耗時較長的同步任務,在worker進程當中投遞過來。
client跟server的交互:
1、client請求到達 Main Reactor,Client實際上是與Master進程中的某個Reactor線程發(fā)生了連接。
2、Main Reactor根據(jù)Reactor的情況,將請求注冊給對應的Reactor
3、客戶端有變化時Reactor將數(shù)據(jù)交給worker來處理
4、worker處理完畢,通過進程間通信(比如管道、共享內(nèi)存、消息隊列)發(fā)給對應的reactor。
5、reactor將響應結果發(fā)給相應的連接請求處理完成
示意圖:
一個更通俗的比喻,假設Server就是一個工廠,那Reactor就是銷售,接受客戶訂單。而Worker就是工人,當銷售接到訂單后,Worker去工作生產(chǎn)出客戶要的東西。而Task_Worker可以理解為行政人員,可以幫助Worker干些雜事,讓Worker專心工作。
進程的綁定事件
Master進程內(nèi)的回調(diào)函數(shù)
- onStart Server啟動在主進程的主線程回調(diào)此函數(shù)
- onShutdown 此事件在Server正常結束時發(fā)生
Manager進程內(nèi)的回調(diào)函數(shù)
- onManagerStart 當管理進程啟動時調(diào)用它
- onManagerStop 當管理進程結束時調(diào)用它
- onWorkerError 當worker/task_worker進程發(fā)生異常后會在Manager進程內(nèi)回調(diào)此函數(shù)
Worker進程內(nèi)的回調(diào)函數(shù)
- onWorkerStart 此事件在Worker進程/Task進程啟動時發(fā)生
- onWorkerStop 此事件在worker進程終止時發(fā)生。
- onConnect 有新的連接進入時,在worker進程中回調(diào)
- onClose TCP客戶端連接關閉后,在worker進程中回調(diào)此函數(shù)
- onReceive 接收到數(shù)據(jù)時回調(diào)此函數(shù),發(fā)生在worker進程中
- onRequest 有新的連接進入時,在worker進程中回調(diào)
- onPacket 接收到UDP數(shù)據(jù)包時回調(diào)此函數(shù),發(fā)生在worker進程中
- onFinish 當worker進程投遞的任務在task_worker中完成時,task進程會通過finish()方法將任務處理的結果發(fā)送給worker進程。
- onWorkerExit 僅在開啟reload_async特性后有效。異步重啟特性
- onPipeMessage 當工作進程收到由 sendMessage 發(fā)送的管道消息時會觸發(fā)事件
Task進程內(nèi)的回調(diào)函數(shù)
- onTask 在task_worker進程內(nèi)被調(diào)用。worker進程可以使用swoole_server_task函數(shù)向task_worker進程投遞新的任務
- onWorkerStart 此事件在Worker進程/Task進程啟動時發(fā)生
- onPipeMessage 當工作進程收到由 sendMessage 發(fā)送的管道消息時會觸發(fā)事件
3.5、swoole運行模式及熱重啟
Swoole之所以性能卓越,是因為Swoole減少了每一次請求加載PHP文件以及初始化的開銷。但是這種優(yōu)勢也導致開發(fā)者無法像過去一樣,修改PHP文件,重新請求,就能獲取到新代碼的運行結果。如果需要新代碼開始執(zhí)行,往往需要先關閉服務器然后重啟,這樣才能使得新文件被加載進內(nèi)存運行,這樣很明顯不能滿足開發(fā)者的需求。幸運的是,Swoole提供了這樣的功能。
在swoole中,我們可以向主進程發(fā)送各種不同的信號,主進程根據(jù)接收到的信號類型做出不同的處理。比如下面這幾個
1、kill -SIGTERM master_pid 終止Swoole程序,一種優(yōu)雅的終止信號,會待進程執(zhí)行完當前程序之后中斷,而不是直接干掉進程
2、kill -USR1 master_pid 重啟所有的Worker進程
3、kill -USR2|-12 master_pid 重啟所有的Task Worker進程
當USR1信號被發(fā)送給Master進程后,Master進程會將同樣的信號通過Manager進程轉(zhuǎn)發(fā)Worker進程,收到此信號的Worker進程會在處理完正在執(zhí)行的邏輯之后,釋放進程內(nèi)存,關閉自己,然后由Manager進程重啟一個新的Worker進程。新的Worker進程會占用新的內(nèi)存空間,重新加載文件。
具體場景:
如果是上線的項目,一臺繁忙的后端服務器隨時都在處理請求,如果管理員通過kill進程方式來終止/重啟服務器程序,可能導致剛好代碼執(zhí)行到一半終止。
這種情況下會產(chǎn)生數(shù)據(jù)的不一致。如交易系統(tǒng)中,支付邏輯的下一段是發(fā)貨,假設在支付邏輯之后進程被終止了。會導致用戶支付了貨幣,但并沒有發(fā)貨,后果非常嚴重。
如何解決?
這個時候我們需要考慮如何平滑重啟server的問題了。所謂的平滑重啟,也叫“熱重啟”,就是在不影響用戶的情況下重啟服務,更新內(nèi)存中已經(jīng)加載的php程序代碼,從而達到對業(yè)務邏輯的更新。
swoole為我們提供了平滑重啟機制,我們只需要向swoole_server的主進程發(fā)送特定的信號,即可完成對server的重啟。
注意事項:
1、更新僅僅只是針對worker進程,也就是寫在master進程跟manger進程當中更新代碼并不生效,也就是說只有在onWorkerStart回調(diào)之后加載的文件,重啟才有意義。在Worker進程啟動之前就已經(jīng)加載到內(nèi)存中的文件,如果想讓它重新生效,只能關閉server再重啟。
2、直接寫在worker代碼當中的邏輯是不會生效的,就算發(fā)送了信號也不會,需要通過include方式引入相關的業(yè)務邏輯代碼才會生效
四、為什么需要分布式服務
4.1、早期單體架構帶來的問題
單體架構在規(guī)模比較小的情況下工作情況良好,但是隨著系統(tǒng)規(guī)模的擴大,它暴露出來的問題也越來越多,主要有以下幾點:
1.復雜性逐漸變高
比如有的項目有幾十萬行代碼,各個模塊之間區(qū)別比較模糊,邏輯比較混亂,代碼越多復雜性越高,越難解決遇到的問題。
2.技術債務逐漸上升
公司的人員流動是再正常不過的事情,有的員工在離職之前,疏于代碼質(zhì)量的自我管束,導致留下來很多坑,由于單體項目代碼量龐大的驚人,留下的坑很難被發(fā)覺,這就給新來的員工帶來很大的煩惱,人員流動越大所留下的坑越多,也就是所謂的技術債務越來越多。
3.阻礙技術創(chuàng)新
比如以前的某個項目使用tp3.2寫的,由于各個模塊之間有著千絲萬縷的聯(lián)系,代碼量大,邏輯不夠清楚,如果現(xiàn)在想用tp5來重構這個項目將是非常困難的,付出的成本將非常大,所以更多的時候公司不得不硬著頭皮繼續(xù)使用老的單體架構,這就阻礙了技術的創(chuàng)新。
4.無法按需伸縮
比如說推薦模塊是CPU密集型的模塊,而訂單模塊是IO密集型的模塊,假如我們要提升訂單模塊的性能,比如加大內(nèi)存、增加硬盤,但是由于所有的模塊都在一個架構下,因此我們在擴展訂單模塊的性能時不得不考慮其它模塊的因素,因為我們不能因為擴展某個模塊的性能而損害其它模塊的性能,從而無法按需進行伸縮。
5.系統(tǒng)高可用性差
因為所有的功能開發(fā)最后都部署到同一個框架里,運行在同一個進程之中,一旦某一功能涉及的代碼或者資源有問題,那就會影響整個框架中部署的功能。
五、什么是RPC?
RPC(Remote Procedure Call)—遠程過程調(diào)用,它是一種通過網(wǎng)絡從遠程計算機程序上請求服務,而不需要了解底層網(wǎng)絡技術的協(xié)議。
比如說兩臺服務器A,B,一個應用部署在A服務器上,想要調(diào)用B服務器上應用提供的函數(shù)/方法,由于不在一個內(nèi)存空間,不能直接調(diào)用,就需要通過網(wǎng)絡來表達調(diào)用的語義和傳達調(diào)用的數(shù)據(jù),而這種方式就是rpc
5.1、為什么需要RPC?
RPC 的主要功能目標是讓構建分布式計算(應用)更容易,在提供強大的遠程調(diào)用能力時不損失本地調(diào)用的語義簡潔性。為實現(xiàn)該目標,RPC 框架需提供一種透明調(diào)用機制讓使用者不必顯式的區(qū)分本地調(diào)用和遠程調(diào)用。
Call(“l(fā)istServices”)->info();
rpc隱藏了通訊的細節(jié),調(diào)用遠程的服務就像調(diào)用本地的代碼一樣,其調(diào)用協(xié)議通常包含傳輸協(xié)議和編碼協(xié)議。
傳輸協(xié)議: 可以是自定義的tcp協(xié)議,可以是http、websockect
編碼協(xié)議: 如基于文本編碼的 xml、 json,也有二進制編碼的 protobuf 、binpack 等。
5.2、使用什么協(xié)議?
RPC是一個軟件結構概念,是構建分布式應用的理論基礎。就好比為啥你家可以用到發(fā)電廠發(fā)出 來的電?
是因為電是可以傳輸?shù)?。至于用銅線還是用鐵絲還是其他種類的導線,也就是用http還是用其他協(xié)議的問題了。這個要看什么場景,對性能要求怎么樣。
5.3、rpc就只是接口調(diào)用?
一個完善的rpc其實還包含另一塊內(nèi)容,通信協(xié)議外還有“服務注冊發(fā)現(xiàn)”,錯誤重試,服務限流,服務調(diào)用的負載均衡等等,rpc是不僅僅是一套設計規(guī)范,還包含了服務治理。
5.4 實際操作
傳輸協(xié)議: TCP協(xié)議
編碼協(xié)議: json編碼
以上就是詳解Swoole跟傳統(tǒng)的web開發(fā)的區(qū)別的詳細內(nèi)容,更多關于Swoole跟傳統(tǒng)的web開發(fā)的區(qū)別的資料請關注腳本之家其它相關文章!
您可能感興趣的文章:- 淺談swoole的作用與原理
- 淺談Swoole并發(fā)編程的魅力
- 詳解PHP Swoole長連接常見問題
- 詳解PHP Swoole與TCP三次握手
- Swoole擴展的6種模式深入詳解
- php中Swoole的熱更新實現(xiàn)代碼實例
- swoole鎖的機制代碼實例講解
- Swoole源碼中如何查詢Websocket的連接問題詳解
- PHP swoole的process模塊創(chuàng)建和使用子進程操作示例