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