開(kāi)發(fā)者必讀
- GitHub 喜歡記得點(diǎn)個(gè) star
社區(qū)答疑
-
QQ 交流群
- VIP群 579434607 (本群需要付費(fèi)599元)
- EasySwoole 官方一群 633921431(已滿)
- EasySwoole 官方二群 709134628(已滿)
- EasySwoole 官方三群 932625047(已滿)
- EasySwoole 官方四群 779897753(已滿)
- EasySwoole 官方五群 853946743(已滿)
- EasySwoole 官方六群 524475224(已滿)
- EasySwoole 官方七群 1016674948
-
商業(yè)支持:
- QQ 291323003
- EMAIL admin@fosuss.com
注意事項(xiàng)
- 不要在代碼中執(zhí)行
sleep
以及其他睡眠函數(shù),這樣會(huì)導(dǎo)致整個(gè)進(jìn)程阻塞;協(xié)程中可以使用Co::sleep()
; -
exit/die
是危險(xiǎn)的,會(huì)導(dǎo)致Worker
進(jìn)程退出; - 可通過(guò)
register_shutdown_function
來(lái)捕獲致命錯(cuò)誤,在進(jìn)程異常退出時(shí)做一些清理工作; -
PHP
代碼中如果有異常拋出,必須在回調(diào)函數(shù)中進(jìn)行try/catch
捕獲異常,否則會(huì)導(dǎo)致工作進(jìn)程退出; -
EasySwoole
不支持set_exception_handler
,必須使用try/catch
方式處理異常; - 在控制器中不能寫共享
Redis
或MySQL
等網(wǎng)絡(luò)服務(wù)客戶端連接的邏輯,每次訪問(wèn)控制器都必須new
一個(gè)連接
類/函數(shù)重復(fù)定義
- 新手非常容易犯這個(gè)錯(cuò)誤,由于
EasySwoole
是常駐內(nèi)存的,所以加載類/函數(shù)定義的文件后不會(huì)釋放。因此引入類/函數(shù)的 php 文件時(shí)必須要使用include_once
或require_once
,否則會(huì)發(fā)生cannot redeclare function/class
的致命錯(cuò)誤。
建議使用 composer
做自動(dòng)加載
進(jìn)程隔離與內(nèi)存管理
進(jìn)程隔離也是很多新手經(jīng)常遇到的問(wèn)題。修改了全局變量的值,為什么不生效?原因就是全局變量在不同的進(jìn)程,內(nèi)存空間是隔離的,所以無(wú)效。
所以使用 EasySwoole
開(kāi)發(fā) Server
程序需要了解 進(jìn)程隔離
問(wèn)題。
-
不同的進(jìn)程中
PHP
變量不是共享,即使是全局變量,在A
進(jìn)程內(nèi)修改了它的值,在B
進(jìn)程內(nèi)是無(wú)效的,如果需要在不同的Worker
進(jìn)程內(nèi)共享數(shù)據(jù),可以用Redis
、MySQL
、文件
、Swoole\Table
、APCu
、shmget
等工具實(shí)現(xiàn)Worker
進(jìn)程內(nèi)共享數(shù)據(jù) -
不同進(jìn)程的文件句柄是隔離的,所以在
A
進(jìn)程創(chuàng)建的Socket
連接或打開(kāi)的文件,在B
進(jìn)程內(nèi)是無(wú)效,即使是將它的fd
發(fā)送到B
進(jìn)程也是不可用的。(句柄不能進(jìn)程共享) -
進(jìn)程克隆。在
Server
啟動(dòng)時(shí),主進(jìn)程會(huì)克隆當(dāng)前進(jìn)程狀態(tài),此后開(kāi)始進(jìn)程內(nèi)數(shù)據(jù)相互獨(dú)立,互不影響。有疑問(wèn)的新手可以先弄懂PHP
的pcntl
擴(kuò)展
EasySwoole
中對(duì)象的4層生命周期
開(kāi)發(fā) Swoole
程序與普通 LAMP
下編程有本質(zhì)區(qū)別。在傳統(tǒng)的 Web 編程中,PHP 程序員只需要關(guān)注 request 到達(dá),request 結(jié)束即可。而在 Swoole
程序中程序員可以操控更大范圍,變量/對(duì)象可以有四種生存周期。
變量、對(duì)象、資源、require/include 的文件等下面統(tǒng)稱為對(duì)象
程序全局期
在 EasySwoole
框架根目錄的 bootstrap.php
文件和 EasySwooleEvent.php
文件中的 initialize
事件函數(shù)中創(chuàng)建好的對(duì)象,我們稱之為程序全局生命周期對(duì)象。這些變量只要沒(méi)有被作用域銷毀,在程序啟動(dòng)后就會(huì)一直存在,直到整個(gè)程序結(jié)束運(yùn)行才會(huì)銷毀。
有一些服務(wù)器程序可能會(huì)連續(xù)運(yùn)行數(shù)月甚至數(shù)年才會(huì)關(guān)閉/重啟,那么程序全局期的對(duì)象在這段時(shí)間內(nèi)會(huì)持續(xù)駐留在內(nèi)存中的。程序全局期對(duì)象所占用的內(nèi)存是 Worker
進(jìn)程間共享的,不會(huì)額外占用內(nèi)存。
例如:
- 在
EasySwooleEvent.php
文件中的initialize
事件函數(shù)中使用Di
注入一個(gè)對(duì)象,那么在程序開(kāi)始之后,在EasySwoole
的控制器中,或者其他地方都可以通過(guò)Di
直接調(diào)用這個(gè)對(duì)象 - 在
bootstrap.php
中引入一個(gè)文件test.php
,該文件定義了一個(gè)靜態(tài)變量,那么在EasySwoole
的控制器,或者其他地方都可以調(diào)用這個(gè)靜態(tài)變量
這部分內(nèi)存會(huì)在寫時(shí)分離(COW
),在 Worker
進(jìn)程內(nèi)對(duì)這些對(duì)象進(jìn)行寫操作時(shí),會(huì)自動(dòng)從共享內(nèi)存中分離,變?yōu)檫M(jìn)程全局對(duì)象。
例如:
- 在
EasySwooleEvent.php
文件中的initialize
事件函數(shù)中使用Di
注入一個(gè)對(duì)象,并在用戶A
訪問(wèn)控制器時(shí)修改了這個(gè)對(duì)象的屬性,那么其他用戶訪問(wèn)控制器的時(shí)候,獲取這個(gè)對(duì)象屬性時(shí),可能是未改變的狀態(tài)(因?yàn)椴煌脩粼L問(wèn)的控制器所在的進(jìn)程不同,其他進(jìn)程不會(huì)修改到這個(gè)變量,所以需要注意這個(gè)問(wèn)題); - 在
bootstrap.php
中引入一個(gè)文件test.php
,該文件定義了一個(gè)靜態(tài)變量$a = 1
,用戶A
訪問(wèn)控制器時(shí)修改了變量$a = 2
,可能在其他用戶訪問(wèn)時(shí),依然還是$a = 1
的狀態(tài)。
程序全局期 include/require
的代碼,必須在整個(gè)程序 shutdown
時(shí)才會(huì)釋放,reload
無(wú)效
進(jìn)程全局期
Swoole
擁有進(jìn)程生命周期控制的機(jī)制,Worker
進(jìn)程啟動(dòng)后創(chuàng)建的對(duì)象(onWorkerStart
中創(chuàng)建的對(duì)象或者在控制器中創(chuàng)建的對(duì)象),在這個(gè)子進(jìn)程存活周期之內(nèi),是常駐內(nèi)存的。
例如:
- 程序全局生命周期對(duì)象被控制器修改之后,該對(duì)象會(huì)復(fù)制一份出來(lái)到控制器所屬的進(jìn)程,這個(gè)對(duì)象只能被這個(gè)進(jìn)程訪問(wèn),其他進(jìn)程訪問(wèn)的依舊是全局對(duì)象。
- 給服務(wù)注冊(cè)
onWorkerStart
事件(在EasySwooleEvent.php
中的mainServerCreate
事件中進(jìn)行注冊(cè)onWorkerStart
事件)時(shí)創(chuàng)建的對(duì)象,只會(huì)在該Worker
進(jìn)程才能獲取到。
進(jìn)程全局對(duì)象所占用的內(nèi)存是在當(dāng)前子進(jìn)程內(nèi)存堆的,并非共享內(nèi)存。對(duì)此對(duì)象的修改僅在當(dāng)前 Worker
進(jìn)程中有效,進(jìn)程全局期 include/require
的文件,在 reload
后就會(huì)重新加載
會(huì)話期
會(huì)話期是在 onConnect
后創(chuàng)建,或者在第一次 onReceive
時(shí)創(chuàng)建,onClose
時(shí)銷毀。一個(gè)客戶端連接進(jìn)入后,創(chuàng)建的對(duì)象會(huì)常駐內(nèi)存,直到此客戶端斷開(kāi)連接才會(huì)銷毀。
在 LAMP
中,一個(gè)客戶端瀏覽器訪問(wèn)多次網(wǎng)站,就可以理解為會(huì)話期。但傳統(tǒng) PHP
程序,并不能感知到。只有單次訪問(wèn)時(shí)使用 session_start
,訪問(wèn) $_SESSION
全局變量才能得到會(huì)話期的一些信息。
Swoole
中會(huì)話期的對(duì)象直接是常駐內(nèi)存的,不需要 session_start
之類操作。可以直接訪問(wèn)對(duì)象,并執(zhí)行對(duì)象的方法。
請(qǐng)求期
請(qǐng)求期是指一個(gè)完整的請(qǐng)求發(fā)來(lái),也就是 onReceive
收到請(qǐng)求開(kāi)始處理,直到返回結(jié)果發(fā)送 response
。這個(gè)周期所創(chuàng)建的對(duì)象,會(huì)在請(qǐng)求完成后銷毀。
Swoole
中請(qǐng)求期對(duì)象與普通 PHP
程序中的對(duì)象就是一樣的。請(qǐng)求到來(lái)時(shí)創(chuàng)建,請(qǐng)求結(jié)束后銷毀。
swoole_server 中內(nèi)存管理機(jī)制
swoole_server
啟動(dòng)后內(nèi)存管理的底層原理與普通 php-cli
程序一致。具體請(qǐng)參考 Zend VM
內(nèi)存管理方面的文章。
局部變量
在事件回調(diào)函數(shù)返回后,所有局部對(duì)象和變量會(huì)全部回收,不需要 unset
。如果變量是一個(gè)資源類型,那么對(duì)應(yīng)的資源也會(huì)被 PHP
底層釋放。
function test()
{
$a = new Object;
$b = fopen('/data/t.log', 'r+');
$c = new swoole_client(SWOOLE_SYNC);
$d = new swoole_client(SWOOLE_SYNC);
global $e;
$e['client'] = $d;
}
$a, $b, $c
都是局部變量,當(dāng)此函數(shù) return
時(shí),這3個(gè)變量會(huì)立即釋放,對(duì)應(yīng)的內(nèi)存會(huì)立即釋放,打開(kāi)的 IO
資源文件句柄會(huì)立即關(guān)閉。
$d
也是局部變量,但是 return
前將它保存到了全局變量 $e
,所以不會(huì)釋放。當(dāng)執(zhí)行 unset($e['client'])
時(shí),并且沒(méi)有任何其他 PHP
變量仍然在引用 $d
變量,那么 $d
就會(huì)被釋放。
全局變量
在 PHP
中,有3類全局變量。
- 使用
global
關(guān)鍵詞聲明的變量 - 使用
static
關(guān)鍵詞聲明的類靜態(tài)變量、函數(shù)靜態(tài)變量 -
PHP
的超全局變量,包括$_GET、$_POST、$GLOBALS
等
全局變量和對(duì)象,類靜態(tài)變量,保存在 swoole_server
對(duì)象上的變量不會(huì)被釋放。需要程序員自行處理這些變量和對(duì)象的銷毀工作。
class Test
{
static $array = array();
static $string = '';
}
function onReceive($serv, $fd, $reactorId, $data)
{
Test::$array[] = $fd;
Test::$string .= $data;
}
- 在事件回調(diào)函數(shù)中需要特別注意非局部變量的
array
類型值,某些操作如TestClass::$array[] = "string"
可能會(huì)造成內(nèi)存泄漏,嚴(yán)重時(shí)可能發(fā)生爆內(nèi)存,必要時(shí)應(yīng)當(dāng)注意清理大數(shù)組。 - 在事件回調(diào)函數(shù)中,非局部變量的字符串進(jìn)行拼接操作是必須小心內(nèi)存泄漏,如
TestClass::$string .= $data
,可能會(huì)有內(nèi)存泄漏,嚴(yán)重時(shí)可能發(fā)生爆內(nèi)存。
解決方法
- 同步阻塞并且請(qǐng)求響應(yīng)式無(wú)狀態(tài)的
Server
程序可以設(shè)置max_request
,當(dāng)Worker進(jìn)程/Task進(jìn)程
結(jié)束運(yùn)行時(shí)或達(dá)到任務(wù)上限后進(jìn)程自動(dòng)退出。該進(jìn)程的所有變量/對(duì)象/資源均會(huì)被釋放回收。 - 程序內(nèi)在
onClose
或設(shè)置定時(shí)器及時(shí)使用unset
清理變量,回收資源
內(nèi)存管理部分參照了 Swoole
官方文檔。
約定規(guī)范
- 項(xiàng)目中類名稱與類文件(文件夾)命名,均為大駝峰,變量與類方法為小駝峰。
- 在
HTTP
服務(wù)響應(yīng)中,業(yè)務(wù)邏輯代碼中echo $var
并不會(huì)將$var
內(nèi)容輸出至瀏覽器頁(yè)面相應(yīng)內(nèi)容中,請(qǐng)調(diào)用Response
實(shí)例中的wirte()
方法實(shí)現(xiàn)。