10.swoole基礎-常駐內存以及如何避免內存泄漏


Task初體驗一文中我們提到,server中的代碼修改之后,要先按Ctrl+C終止server再重新啟動下server才會生效,當時我們一言以過之,本節我們主要就來看看這個常駐內存相關的事。

在傳統的web開發模式中,我們知道,每一次php請求,都要經過php文件從磁盤上讀取、初始化、詞法解析、語法解析、編譯等過程,而且還要與nginx或者apache通信,如果再涉及數據庫的交互,還要再算上數據庫的握手、驗權、關閉等操作,可見一次請求的背后其實是有相當繁瑣的過程,無疑,這個過程也就帶來了相當多的開銷!當然,所有的這些資源和內存,在一次請求結束之前,都會得到釋放。

但是,swoole是常駐內存運行的。這有幾點不同,我們分別了解下。

在運行server之后所加載的任何資源,都會一直持續在內存中存在。也就是說假設我們開啟了一個server,有100個client要connect,加載一些配置文件、初始化變量等操作,只有在第一個client連接的時候才有這些操作,后面的client連接的時候就省去了重復加載的過程,直接從內存中讀取就好了。

這樣好不好呢?很明顯非常好,如此一來還可以提升不小的性能。

但是,對開發人員的要求也更高了。因為這些資源常駐內存,並不會像web模式下,在請求結束之后會釋放內存和資源。也就是說我們在操作中一旦沒有處理好,就會發生內存泄漏,久而久之就可能會發生內存溢出。

之前一直對swoole印象不錯,沒想到都是坑。其實這都不算坑,如果你覺得是坑,權且當做是一種提升自身能力的約束好了。

回到我們的開篇提到的問題上,再啰嗦的解釋一遍:server一開始就把我們的代碼加載到內存中了,無論后期我們怎么修改本地磁盤上的代碼,客戶端再次發起請求的時候,永遠都是內存中的代碼在生效,所以我們只能終止server,釋放內存然后再重啟server,重新把新的代碼加載到內存中,如此,明白否?

那有同學要說了,感覺好麻煩,是不是說在swoole中申請的內存啥的都要自己手動unset釋放呢?

對於局部變量,就沒必要操這個心了,swoole會在事件回調函數返回之后釋放。但是對於全局變量你就要悠着點了,因為他們在使用完之后並不會被釋放。不會被釋放?那在php中,這幾種全局變量:global聲明的變量,static聲明的對象屬性或者函數內的靜態變量和超全局變量誰還敢用?一個不小心服務器直接就玩完的節奏!

我們想一下為什么要用全局變量?

是不是就是想全局共享?但是,在多進程開發模式下,進程內的全局變量所用的內存那也是保存在子進程內存堆的,也並非共享內存,所以在swoole開發中我們還是盡量避免使用全局變量!

那我要是非用不可呢?就是樂意,就是想用。

我們看看如何避免內存泄漏。

比如有一個static大數組,用於保存客戶端的連接標識。我們就可以在onClose回調內清理變量。

此外,swoole還提供了max_request機制,我們可以配置max_request和task_max_request這兩個參數來避免內存溢出。

max_request的含義是worker進程的最大任務數,當worker進程處理的任務數超過這個參數時,worker進程會自動退出,如此便達到釋放內存和資源的目的。

不必擔心worker進程退出后,沒“人”處理業務邏輯了,因為我們還有Manager進程,Worker進程退出后Manager進程會重新拉起一個新的Worker進程。

task_max_request針對task進程,含義同max_request。

光溜溜的說了半天,我們來看下是不是這么玩的。

server的代碼簡寫如下

$serv = new swoole_server('127.0.0.1', 9501);

$serv->set([
    'worker_num' => 1,
    'task_worker_num' => 1,
    'max_request' => 3,
    'task_max_request' => 4,
]);
$serv->on('Connect', function ($serv, $fd) {
});
$serv->on('Receive', function ($serv, $fd, $fromId, $data) {
    $serv->task($data);
});
$serv->on('Task', function ($serv, $taskId, $fromId, $data) {

});
$serv->on('Finish', function ($serv, $taskId, $data) {
});
$serv->on('Close', function ($serv, $fd) {
});
$serv->start();

client代碼如下

$client = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC);
$client->connect('127.0.0.1', 9501) || exit("connect failed. Error: {$client->errCode}\n");

// 向服務端發送數據
$client -> send("Just a test.");
$client->close();

為了方便測試,我們開了一個Worker進程,一個Task進程,Worker進程的最大任務設置為3次,Task進程的最大任務設置為4次。

運行server后,在client未請求前我們看下當前的進程結構

注意進程id等於15644和15645哦,這兩個一個是Worker進程,一個是Task進程。Mac下我們就不區分到底誰是誰了。

隨后我們讓客戶端請求3次,再看下結果

有沒有發現原先進程id等於15645的現在變成15680了?請求3次后我們確定是Worker進程自動退出了,並且Manager進程拉起了一個15680的Worker進程。

我們再請求一次,第四次

發現進程id等於15644的Task進程消失了,有一個新的子進程15704被重新創建了。

看來官方沒有騙人,說的都對。

So...原來我在一開始介紹的那么多都是廢話?

不全是,因為max_request參數對server有下面幾種限制條件。

max_request只能用於同步阻塞、無狀態的請求響應式服務器程序
純異步的Server不應當設置max_request
使用Base模式時max_request是無效的
其中Base模式是swoole運行模式的一種,我們主要介紹多進程模式。

總結:

  1. 常駐內存減少了不小開銷,swoole不錯
  2. 應盡量避免使用全局變量,不用最好,沒啥用
  3. max_request可以解決php的內存溢出問題,但是主要還是要養成釋放內存的習慣,因為max_request也有限制場景


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM