一、進程的基本知識
什么是進程,所謂進程其實就是操作系統(tǒng)中一個正在運行的程序,我們在一個終端當中,通過php,運行一個php文件,這個時候就相當于我們創(chuàng)建了一個進程,這個進程會在系統(tǒng)中駐存,申請屬于它自己的內存空間系統(tǒng)資源并且運行相應的程序
對于一個進程來說,它的核心內容分為兩個部分,一個是它的內存,這個內存是這進程創(chuàng)建之初從系統(tǒng)分配的,它所有創(chuàng)建的變量都會存儲在這一片內存環(huán)境當中
一個是它的上下文環(huán)境我們知道進程是運行在操作系統(tǒng)的,那么對于程序來說,它的運行依賴操作系統(tǒng)分配給它的資源,操作系統(tǒng)的一些狀態(tài)。
在操作系統(tǒng)中可以運行多個進程的,對于一個進程來說,它可以創(chuàng)建自己的子進程,那么當我們在一個進程中創(chuàng)建出若干個子進程的時候那么可以看到如圖,子進程和父進程一樣,擁有自己的內存空間和上下文環(huán)境
二、Swoole進程結構
Swoole的高效不僅僅于底層使用c編寫,他的進程結構模型也使其可以高效的處理業(yè)務,我們想要深入學習,并且在實際的場景當中使用必須了解,下面我們先看一下結構圖
首先先介紹下swoole的這幾種進程分別是干什么的
從這些層級的名字,我們先大概說一下,下面這些層級分別是干什么的,做一個詳細的說明。
1、Master進程:主進程
2、Manger進程:管理進程
3、Worker進程:工作進程
4、Task進程:異步任務工作進程
1、Master進程
第一層,Master進程,這個是swoole的主進程,這個進程是用于處理swoole的核心事件驅動的,那么在這個進程當中可以看到它擁有一個MainReactor[線程]以及若干個Reactor[線程],swoole所有對于事件的監(jiān)聽都會在這些線程中實現(xiàn),比如來自客戶端的連接,信號處理等。
每一個線程都有自己的用途,下面多每個線程有一個了解
1.1、MainReactor(主線程)
主線程會負責監(jiān)聽server socket,如果有新的連接accept,主線程會評估每個Reactor線程的連接數(shù)量。將此連接分配給連接數(shù)最少的reactor線程,做一個負載均衡。
1.2 、Reactor線程組
Reactor線程負責維護客戶端機器的TCP連接、處理網絡IO、收發(fā)數(shù)據(jù)完全是異步非阻塞的模式。
swoole的主線程在Accept新的連接后,會將這個連接分配給一個固定的Reactor線程,在socket可讀時讀取數(shù)據(jù),并進行協(xié)議解析,將請求投遞到Worker進程。在socket可寫時將數(shù)據(jù)發(fā)送給TCP客戶端。
1.3、心跳包檢測線程(HeartbeatCheck)
Swoole配置了心跳檢測之后,心跳包線程會在固定時間內對所有之前在線的連接
發(fā)送檢測數(shù)據(jù)包
1.4、UDP收包線程(UdpRecv)
接收并且處理客戶端udp數(shù)據(jù)包
2、管理進程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進程的關系。
3、Worker進程
worker 進程屬于swoole的主邏輯進程,用戶處理客戶端的一系列請求,接受由Reactor線程投遞的請求數(shù)據(jù)包,并執(zhí)行PHP回調函數(shù)處理數(shù)據(jù)生成響應數(shù)據(jù)并發(fā)給Reactor線程,由Reactor線程發(fā)送給TCP客戶端可以是異步非阻塞模式,也可以是同步阻塞模式
4、Task進程
taskWorker進程這一進城是swoole提供的異步工作進程,這些進程主要用于處理一些耗時較長的同步任務,在worker進程當中投遞過來。
二、進程查看及流程梳理
當啟動一個Swoole應用時,一共會創(chuàng)建2 + n + m個進程,2為一個Master進程和一個Manager進程,其中n為Worker進程數(shù)。m為TaskWorker進程數(shù)。
默認如果不設置,swoole底層會根據(jù)當前機器有多少CPU核數(shù),啟動對應數(shù)量的Reactor線程和Worker進程。我機器為1核的。Worker為1。
所以現(xiàn)在默認我啟動了1個Master進程,1個Manager進程,和1個worker進程,TaskWorker沒有設置也就是為0,當前server會產生3個進程。
在啟動了server之后,在命令行查看當前產生的進程
這三個進程中,所有進程的根進程,也就是例子中的2123進程,就是所謂的Master進程;而2212進程,則是Manager進程;最后的2321進程,是Worker進程。
2.2、client跟server的交互:
1、client請求到達 Main Reactor,Client實際上是與Master進程中的某個Reactor線程發(fā)生了連接。
2、Main Reactor根據(jù)Reactor的情況,將請求注冊給對應的Reactor (每個Reactor都有epoll。用來監(jiān)聽客戶端的變化)
3、客戶端有變化時Reactor將數(shù)據(jù)交給worker來處理
4、worker處理完畢,通過進程間通信(比如管道、共享內存、消息隊列)發(fā)給對應的reactor。
5、reactor將響應結果發(fā)給相應的連接請求處理完成
三、進程的綁定事件
大概了解下,后面會相應的觸發(fā)這些事件
Master進程內的回調函數(shù)
onStart Server啟動在主進程的主線程回調此函數(shù)
nShutdown 此事件在Server正常結束時發(fā)生
Manager進程內的回調函數(shù)
onManagerStart 當管理進程啟動時調用它
onManagerStop 當管理進程結束時調用它
onWorkerError 當worker/task_worker進程發(fā)生異常后會在Manager進程內回調此函數(shù)
Worker進程內的回調函數(shù)
onWorkerStart 此事件在Worker進程/Task進程啟動時發(fā)生
onWorkerStop 此事件在worker進程終止時發(fā)生。
onConnect 有新的連接進入時,在worker進程中回調
onClose TCP客戶端連接關閉后,在worker進程中回調此函數(shù)
onReceive 接收到數(shù)據(jù)時回調此函數(shù),發(fā)生在worker進程中
[onPacket](https://wiki.swoole.com/wiki/page/450.html) 接收到UDP數(shù)據(jù)包時回調此函數(shù),發(fā)生在worker進程中
onFinish 當worker進程投遞的任務在task_worker中完成時,task進程會通過finish()方法將任務處理的結果發(fā)送給worker進程。
onWorkerExit 僅在開啟reload_async特性后有效。異步重啟特性
onPipeMessage 當工作進程收到由 [sendMessage](https://wiki.swoole.com/wiki/page/363.html) 發(fā)送的管道消息時會觸發(fā)事件
Task進程內的回調函數(shù)
onTask 在task_worker進程內被調用。worker進程可以使用swoole_server_task函數(shù)向task_worker進程投遞新的任務
onWorkerStart 此事件在Worker進程/Task進程啟動時發(fā)生
onPipeMessage 當工作進程收到由 [sendMessage](https://wiki.swoole.com/wiki/page/363.html) 發(fā)送的管道消息時會觸發(fā)事件
簡單說明:
1、服務器關閉程序終止時最后一次事件是onShutdown。
2、服務器啟動成功后,onStart/onManagerStart/onWorkerStart會在不同的進程內并發(fā)執(zhí)
行,并不是順序的。
3、所有事件回調均在$server->start后發(fā)生,start之后寫的代碼是無效代碼。
4、onStart/onManagerStart/onWorkerStart 3個事件的執(zhí)行順序是不確定的
Swoole的Reactor、Worker、TaskWorker之間可以緊密的結合起來,提供更高級的使用方式。
一個更通俗的比喻,假設Server就是一個工廠,那Reactor就是銷售,接受客戶訂單。而Worker就是工人,當銷售接到訂單后,Worker去工作生產出客戶要的東西。而TaskWorker可以理解為行政人員,可以幫助Worker干些雜事,讓Worker專心工作。
四、守護進程、信號和平滑重啟
4.1 守護進程
我們現(xiàn)在開啟的server,不管我們程序寫的多么精彩,都沒有辦法把項目應用到實際業(yè)務中,只要把運行server的終端關閉之后,server也就不復存在了。
守護進程(daemon)就是一種長期生存的進程,它不受終端的控制,可以在后臺運行。其實我們之前也有了解,比如說nginx,fpm等一般都是作為守護進程在后臺提供服務。
Swoole實現(xiàn)守護進程
啟用守護進程后,server內所有的標準輸出都會被丟棄,這樣的話我們也就無法跟蹤進程在運行過程中是否異常之類的錯誤信息了。一般會配合log_file我們可以指定日志路徑,這樣swoole在運行時就會把所有的標準輸出統(tǒng)統(tǒng)記載到該文件內。
4.2swoole運行模式及熱重啟
Swoole之所以性能卓越,是因為Swoole減少了每一次請求加載PHP文件以及初始化的開銷。但是這種優(yōu)勢也導致開發(fā)者無法像過去一樣,修改PHP文件,重新請求,就能獲取到新代碼的運行結果(具體看另外的課程文檔)。如果需要新代碼開始執(zhí)行,往往需要先關閉服務器然后重啟,這樣才能使得新文件被加載進內存運行,這樣很明顯不能滿足開發(fā)者的需求。幸運的是,Swoole 提供了這樣的功能。
具體場景:
如果是上線的項目,一臺繁忙的后端服務器隨時都在處理請求,如果管理員通過kill進程方式來終止/重啟服務器程序,可能導致剛好代碼執(zhí)行到一半終止。
這種情況下會產生數(shù)據(jù)的不一致。如交易系統(tǒng)中,支付邏輯的下一段是發(fā)貨,假設在支付邏輯之后進程被終止了。會導致用戶支付了貨幣,但并沒有發(fā)貨,后果非常嚴重。
如何解決?
這個時候我們需要考慮如何平滑重啟server的問題了。所謂的平滑重啟,也叫“熱重啟”,就是在不影響用戶的情況下重啟服務,更新內存中已經加載的php程序代碼,從而達到對業(yè)務邏輯的更新。
swoole為我們提供了平滑重啟機制,我們只需要向swoole_server的主進程發(fā)送特定的信號,即可完成對server的重啟。
那什么是信號呢?
信號的名字都以“SIG”開頭,比如我們最熟悉的Ctrl+C就是一個名字叫“SIGINT”的信號,意味著“終端中斷”。
在swoole中,我們可以向主進程發(fā)送各種不同的信號,主進程根據(jù)接收到的信號類型做出不同的處理。比如下面這幾個
1、kill -SIGTERM|-15 master_pid 終止Swoole程序,一種優(yōu)雅的終止信號,會待進程執(zhí)行完當前程序之后中斷,而不是直接干掉進程
2、kill -USR1|-10 master_pid 重啟所有的Worker進程
3、kill -USR2|-12 master_pid 重啟所有的Task Worker進程
當USR1信號被發(fā)送給Master進程后,Master進程會將同樣的信號通過Manager進程轉發(fā)Worker進程,收到此信號的Worker進程會在處理完正在執(zhí)行的邏輯之后,釋放進程內存,關閉自己,然后由Manager進程重啟一個新的Worker進程。新的Worker進程會占用新的內存空間。
注意事項:
1、更新僅僅只是針對worker進程,也就是寫在master進程跟manger進程當中更新代碼并不生效,也就是說只有在onWorkerStart回調之后加載的文件,重啟才有意義。在Worker進程啟動之前就已經加載到內存中的文件,如果想讓它重新生效,只能關閉server再重啟。
2、直接寫在worker代碼當中的邏輯是不會生效的,就算發(fā)送了信號也不會,需要通過include方式引入相關的業(yè)務邏輯代碼才會生效
實際操作
1、首先,我們需要在程序中注冊自動加載函數(shù),通過這些自動加載函數(shù)實現(xiàn)邏輯文件的更新。
2、其次,我們需要保存服務的Master進程的進程號在目錄下創(chuàng)建一個server.pid文件來保存,并在需要重新加載新文件的時候,向Master進程發(fā)送USR1信號。當Worker進程重啟后,之前加載過的文件就從內存中移除,下一次請求時就會重新加載新的文件。
注意:
1、OnWorkerStart之后加載的代碼都在各自進程中,OnWorkerStart之前加載的代碼屬于共享內存。
2、可以將公用的,不易變的php文件放置到onWorkerStart之前。這樣雖然不能重載入代碼,但所有worker是共享的,不需要額外的內存來保存這些數(shù)據(jù)。onWorkerStart之后的代碼每個worker都需要在內存中保存一份