一、swoole的運作模式 [1]
Swoole高效跟傳統的web開發有什麼差別,除了傳統的LAMP/LNMP同步開發模式,swoole的異步開發模式是怎麼樣的。
我的官方群點選此處
。擷取更多的swoole學習資料以及視訊源碼筆記。1.1、傳統web開發模式
PHP web開發采用的方式是LAMP/LNMP架構,即Linux、Nginx,Mysql和PHP。這裡以nginx來舉例,大緻結構為:

當請求進入時,web server将請求轉交給PHP-FPM,PHP-FPM是一個程序池架構的FastCGI服務,内置PHP解釋器。FPM負責解釋執行PHP檔案生成響應,最終傳回給web server,展現至前端。PHP檔案中實作了許多業務邏輯,包括Mysql和Nosql的通路,調用第三方應用等等。
這樣的結構php-fpm和nginx的配合已經運作得足夠好,但是由于php-fpm本身是同步阻塞程序模型,在請求結束後釋放所有的資源(包括架構初始化建立的一系列對象),導緻PHP程序“空轉”(建立<-->銷毀<-->建立)消耗大量的CPU資源,進而導緻單機的吞吐能力有限。
每次請求處理的過程都意味着一次PHP檔案解析,環境設定等不必要的耗時操作PHP程序處理完即銷毀,無法在PHP程式中使用連接配接池等技術實作性能優化。
1.2、Swoole運作模式
針對傳統架構的問題,swoole從PHP擴充出發,解決了上述問題,對于swoole的程序模型,我們剛剛已經學過了。
相比于傳統架構,Swoole程序模型最大的特點在于其多線程Reactor模式處理網絡請求,使得其能輕松應對大量連接配接。
除此之外的優點還包括:
全異步非阻塞,占用資源開銷小,程式執行效率高
程式運作隻解析加載一次PHP檔案,避免每次請求的重複加載
1.3、使用swoole和傳統php開發的缺點
1、更難上手。這要求開發人員對于多程序的運作模式有更清晰的認識
2、更容易記憶體洩露。在處理全局變量,靜态變量的時候一定要小心,這種不會被GC清理的變量會存在整個生命周期中,如果沒有正确的處理,很容易消耗完所有的記憶體。在php-fpm下,php代碼執行完記憶體就會被完全釋放。
二、注解機制
一般而言,在程式設計屆中注解是一種和注釋平行的概念,在解釋注解之前我們需要先定義一下 注解 與 注釋 的差別:
注釋:給程式員看,幫助了解代碼,對代碼起到解釋、說明的作用。
注解:給應用程式看,注解往往充當着對代碼的聲明和配置的作用,為可執行代碼提供機器可用的額外資訊,在特定的環境下會影響程式的執行。
架構可以基于這些元資訊為代碼提供各種額外功能,本質上注解就是了解注解隻是配置的另一種展現方式:
比如通過注解的方式實作權限的控制,就比配置檔案當中配置要更加的友善
比如利用注解的方式配置路由、配置定時任務
現有的基于swoole的架構很多都是基于注解開發的,是以我們需要對注解機制有了解,接下來利用代碼來實作下注解
二、容器
3.1、什麼是容器?容器 就是一個巨大的工廠,用于存放和管理 對象的生命周期,并且能夠解決程式的依賴關系,實作解耦。
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){
//假設資料庫驅動發生了變化呢?如果寫死隻能直接改動代碼
$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類依賴的資料庫連接配接對象,通過類名直接調用靜态方法get_db
$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.3、為什麼說在swoole當中使用容器更有意義?傳統的php架構沒有常駐記憶體,是以每次請求進來都需要把用到的類都執行個體化一次,每次執行個體化都需要申請記憶體,當請求處理完之後又需要釋放,具體請參看第一點,是以我們可以在server啟動的時候就把類執行個體化預先放到記憶體中,減入對象的建立時間。
一個簡單的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編寫,他的程序結構模型也使其可以高效的處理業務,我們想要深入學習,并且在實際的場景當中使用必須了解,下面我們先看一下結構圖
首先先介紹下swoole的這幾種程序分别是幹什麼的
從這些層級的名字,我們先大概說一下,下面這些層級分别是幹什麼的,做一個詳細的說明。
1、Master程序:主程序
2、Manger程序:管理程序
3、Worker程序:工作程序
4、Task程序:異步任務工作程序
1、Master程序
第一層,Master程序,這個是swoole的主程序,這個程序是用于處理swoole的核心事件驅動的,那麼在這個程序當中可以看到它擁有一個MainReactor[線程]以及若幹個Reactor[線程],swoole所有對于事件的監聽都會在這些線程中實作,比如來自用戶端的連接配接,信号處理等。
每一個線程都有自己的用途,下面多每個線程有一個了解
1.1、MainReactor(主線程)
主線程會負責監聽server socket,如果有新的連接配接accept,主線程會評估每個Reactor線程的連接配接數量。将此連接配接配置設定給連接配接數最少的reactor線程,做一個負載均衡。
1.2 、Reactor線程組
Reactor線程負責維護用戶端機器的TCP連接配接、處理網絡IO、收發資料完全是異步非阻塞的模式。
swoole的主線程在Accept新的連接配接後,會将這個連接配接配置設定給一個固定的Reactor線程,在socket可讀時讀取資料,并進行協定解析,将請求投遞到Worker程序。在socket可寫時将資料發送給TCP用戶端。
1.3、心跳包檢測線程(HeartbeatCheck)
Swoole配置了心跳檢測之後,心跳包線程會在固定時間内對所有之前線上的連接配接
發送檢測資料包
1.4、UDP收包線程(UdpRecv)
接收并且處理用戶端udp資料包
2、管理程序Manager
Swoole想要實作最好的性能必須建立出多個工作程序幫助處理任務,但Worker程序就必須fork操作,但是fork操作是不安全的,如果沒有管理會出現很多的僵屍程序,進而影響伺服器性能,同時worker程序被誤殺或者由于程式的原因會異常退出,為了保證服務的穩定性,需要重新建立worker程序。
Swoole在運作中會建立一個單獨的管理程序,所有的worker程序和task程序都是從管理程序Fork出來的。管理程序會監視所有子程序的退出事件,當worker程序發生緻命錯誤或者運作生命周期結束時,管理程序會回收此程序,并建立新的程序。換句話也就是說,對于worker、task程序的建立、回收等操作全權有“保姆”Manager程序進行管理。
再來一張圖梳理下Manager程序和Worker/Task程序的關系。
3、Worker程序
worker 程序屬于swoole的主邏輯程序,使用者處理用戶端的一系列請求,接受由Reactor線程投遞的請求資料包,并執行PHP回調函數處理資料生成響應資料并發給Reactor線程,由Reactor線程發送給TCP用戶端可以是異步非阻塞模式,也可以是同步阻塞模式
4、Task程序
taskWorker程序這一進城是swoole提供的異步工作程序,這些程序主要用于處理一些耗時較長的同步任務,在worker程序當中投遞過來。
5、client跟server的互動:
1、client請求到達 Main Reactor,Client實際上是與Master程序中的某個Reactor線程發生了連接配接。
2、Main Reactor根據Reactor的情況,将請求注冊給對應的Reactor
3、用戶端有變化時Reactor将資料交給worker來處理
4、worker處理完畢,通過程序間通信(比如管道、共享記憶體、消息隊列)發給對應的reactor。
5、reactor将響應結果發給相應的連接配接請求處理完成
示意圖:
一個更通俗的比喻,假設Server就是一個工廠,那Reactor就是銷售,接受客戶訂單。而Worker就是勞工,當銷售接到訂單後,Worker去工作生産出客戶要的東西。而Task_Worker可以了解為行政人員,可以幫助Worker幹些雜事,讓Worker專心工作。
6、程序的綁定事件
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 發送的管道消息時會觸發事件
3.5、swoole運作模式及熱重新開機
Swoole之是以性能卓越,是因為Swoole減少了每一次請求加載PHP檔案以及初始化的開銷。但是這種優勢也導緻開發者無法像過去一樣,修改PHP檔案,重新請求,就能擷取到新代碼的運作結果。如果需要新代碼開始執行,往往需要先關閉伺服器然後重新開機,這樣才能使得新檔案被加載進記憶體運作,這樣很明顯不能滿足開發者的需求。幸運的是,Swoole提供了這樣的功能。
在swoole中,我們可以向主程序發送各種不同的信号,主程序根據接收到的信号類型做出不同的處理。比如下面這幾個
1、kill -SIGTERM master_pid 終止Swoole程式,一種優雅的終止信号,會待程序執行完目前程式之後中斷,而不是直接幹掉程序
2、kill -USR1 master_pid 重新開機所有的Worker程序
3、kill -USR2|-12 master_pid 重新開機所有的Task Worker程序
當USR1信号被發送給Master程序後,Master程序會将同樣的信号通過Manager程序轉發Worker程序,收到此信号的Worker程序會在處理完正在執行的邏輯之後,釋放程序記憶體,關閉自己,然後由Manager程序重新開機一個新的Worker程序。新的Worker程序會占用新的記憶體空間,重新加載檔案。
具體場景:
如果是上線的項目,一台繁忙的後端伺服器随時都在處理請求,如果管理者通過kill程序方式來終止/重新開機伺服器程式,可能導緻剛好代碼執行到一半終止。
這種情況下會産生資料的不一緻。如交易系統中,支付邏輯的下一段是發貨,假設在支付邏輯之後程序被終止了。會導緻使用者支付了貨币,但并沒有發貨,後果非常嚴重。
如何解決?
這個時候我們需要考慮如何平滑重新開機server的問題了。所謂的平滑重新開機,也叫“熱重新開機”,就是在不影響使用者的情況下重新開機服務,更新記憶體中已經加載的php程式代碼,進而達到對業務邏輯的更新。
swoole為我們提供了平滑重新開機機制,我們隻需要向swoole_server的主程序發送特定的信号,即可完成對server的重新開機。
注意事項:
1、更新僅僅隻是針對worker程序,也就是寫在master程序跟manger程序當中更新代碼并不生效,也就是說隻有在onWorkerStart回調之後加載的檔案,重新開機才有意義。在Worker程序啟動之前就已經加載到記憶體中的檔案,如果想讓它重新生效,隻能關閉server再重新開機。
2、直接寫在worker代碼當中的邏輯是不會生效的,就算發送了信号也不會,需要通過include方式引入相關的業務邏輯代碼才會生效
四、為什麼需要分布式服務4.1、早期單體架構帶來的問題
單體架構在規模比較小的情況下工作情況良好,但是随着系統規模的擴大,它暴露出來的問題也越來越多,主要有以下幾點:
1.複雜性逐漸變高比如有的項目有幾十萬行代碼,各個子產品之間差別比較模糊,邏輯比較混亂,代碼越多複雜性越高,越難解決遇到的問題。
2.技術債務逐漸上升公司的人員流動是再正常不過的事情,有的員工在離職之前,疏于代碼品質的自我管束,導緻留下來很多坑,由于單體項目代碼量龐大的驚人,留下的坑很難被發覺,這就給新來的員工帶來很大的煩惱,人員流動越大所留下的坑越多,也就是所謂的技術債務越來越多。
3.阻礙技術創新比如以前的某個項目使用tp3.2寫的,由于各個子產品之間有着千絲萬縷的聯系,代碼量大,邏輯不夠清楚,如果現在想用tp5來重構這個項目将是非常困難的,付出的成本将非常大,是以更多的時候公司不得不硬着頭皮繼續使用老的單體架構,這就阻礙了技術的創新。
4.無法按需伸縮比如說推薦子產品是CPU密集型的子產品,而訂單子產品是IO密集型的子產品,假如我們要提升訂單子產品的性能,比如加大記憶體、增加硬碟,但是由于所有的子產品都在一個架構下,是以我們在擴充訂單子產品的性能時不得不考慮其它子產品的因素,因為我們不能因為擴充某個子產品的性能而損害其它子產品的性能,進而無法按需進行伸縮。
5.系統高可用性差因為所有的功能開發最後都部署到同一個架構裡,運作在同一個程序之中,一旦某一功能涉及的代碼或者資源有問題,那就會影響整個架構中部署的功能。
五、什麼是RPC?
RPC(Remote Procedure Call)—遠端過程調用,它是一種通過網絡從遠端計算機程式上請求服務,而不需要了解底層網絡技術的協定。
比如說兩台伺服器A,B,一個應用部署在A伺服器上,想要調用B伺服器上應用提供的函數/方法,由于不在一個記憶體空間,不能直接調用,就需要通過網絡來表達調用的語義和傳達調用的資料,而這種方式就是rpc
5.1、為什麼需要RPC?RPC 的主要功能目标是讓建構分布式計算(應用)更容易,在提供強大的遠端調用能力時不損失本地調用的語義簡潔性。為實作該目标,RPC 架構需提供一種透明調用機制讓使用者不必顯式的區分本地調用和遠端調用。
Call(“listServices”)->info();
rpc隐藏了通訊的細節,調用遠端的服務就像調用本地的代碼一樣,其調用協定通常包含傳輸協定和編碼協定。
傳輸協定: 可以是自定義的tcp協定,可以是http、websockect
編碼協定: 如基于文本編碼的 xml、 json,也有二進制編碼的 protobuf 、binpack 等。
5.2、使用什麼協定?RPC是一個軟體結構概念,是建構分布式應用的理論基礎。就好比為啥你家可以用到發電廠發出 來的電?
是因為電是可以傳輸的。至于用銅線還是用鐵絲還是其他種類的導線,也就是用http還是用其他協定的問題了。這個要看什麼場景,對性能要求怎麼樣。
5.3、rpc就隻是接口調用?一個完善的rpc其實還包含另一塊内容,通信協定外還有“服務注冊發現”,錯誤重試,服務限流,服務調用的負載均衡等等,rpc是不僅僅是一套設計規範,還包含了服務治理。
5.4 實際操作
傳輸協定: TCP協定
編碼協定: json編碼
希望以上内容能幫助到大家,加入我的官方群點選此處
。擷取更多的swoole學習資料以及視訊源碼筆記。參考
- ^内容不錯的話希望大家支援鼓勵下點個贊/喜歡,歡迎一起來交流;另外如果有什麼問題和想看的内容可以在評論提出