十四、Swoole 基础学习笔记 - Swoole 如何避免内存泄露

作者: 温新

分类: 【Swoole 系列】

阅读: 1356

时间: 2023-03-13 11:15:25

hi,我是温新,一名PHPer

文章基于 Swoole 5.0.1 版本编写。

**学习目标:当发生内存泄露时知道如何处理 **

说明:本篇文章结合官方文档编写及参考网络资料编写,虽非全部原创,但也是结合了自己的理解,若转载请附带本文 URL,编写不易,持续编写更不易,谢谢!

缘起

在之前的文章中,学习过一个参数 max_request,这篇文章的主题就与它有关系。学习它之前先来回顾一下传统 web,极对比一下与 swoole 的差异。

在传统的 Web 开发模式中,每一次 PHP 请求结束后,其相关资源都会得到释放。但是在这个请求的过程中却要经历 N 多个流程:接收到请求过,读取磁盘上的 PHP 文件、初始化、词法解析、语法解析、编译等过程;与 nginx 或 apache 通信;与数据库进程交互等,一个请求的背后有着相当繁琐的流程。每一个请求都要如此。

再来看看 Swoole,它是常驻内存运行。Server 启动后,所有资源都会在内存中一直存在。如,开启一个 Server 服务,有 10 个客户端请求连接,其中需要的配置文件等操作只有在第一个客户端连接进来时才会操作,其余客户端所需要的配置等资源直接从内存中读取。直观上看确实挺高了性能,事实上确实提高了性能。但由于是常驻内存,请求结束后并不会释放资源,因此,一旦没有处理好请求,那么可能会导致内存泄露。

当启动一个 Server 后,我们重新修改代码,会发现新加的代码没有生效,需要中断 Server 服务,然后重新启动,原因就是 Swoole 常驻内存,服务一旦启动,便将所有代码加载到内存中,而我们修改的代码此时还处于本地。

什么样的情况会发生内存泄露的风险?当使用了全局变量时,可能会造成内存泄露。

在使用 Swoole 时,PHP 中有 3 种全局变量不建议使用:

  • 1、使用 global 关键词声明的变量;
  • 2、使用 static 关键词声明的类静态变量、函数静态变量;
  • 3、PHP 的超全局变量,包括 $_GET$_POST$GLOBALS 等。

全局变量和对象,类静态变量,保存在 Server 对象上的变量不会被释放。

避免内存泄露的处理方法

在 Swoole 中,可以使用 max_requesttask_max_request 来避免内存泄露。这个两个参数如下:

  • max_request:表示 worker 进程的最大任务数量,当 worker 进程处理的任务数量超过这个参数值时,worker 进程自动退出,如此就达到了释放内存和资源的目的;
  • task_max_request:同 max_request 一样。

服务端代码

<?php
// 13-swoole-server.php

$server = new Swoole\Server('0.0.0.0', 9501, SWOOLE_PROCESS);

$server->set([
	'worker_num'       => 1,
	'task_worker_num'  => 1,
	'max_request'      => 2,
	'task_max_reqeust' => 2,
]);


$server->on('Receive', function ($server, $fd, $fromId, $data) {
	$server->task($data);
});

$server->on('Task', function ($server, $taskId, $fromId, $data) {
});

$server->on('Finish', function ($server, $taskId, $data) {
});

$server->start();

为了方便测试,上述代码设置一个 worker 进程,一个 Task 进程,每个进程最大处理任务数量是 2 个请求。下面我们对参数进行验证,先来看一个进程 ID:

$netstat -nltp | grep 9501
tcp        0      0 0.0.0.0:9501            0.0.0.0:*               LISTEN      37925/php

$pstree -p 37925
php(37925)─┬─php(37926)─┬─php(37928)
           │            └─php(37929)
           └─{php}(37927)

得到进程 ID 后,运行客户端进行连接测试,客户端代码如下:

<?php
// 13-swoole-client.php

$client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC);


if (!$client->connect('127.0.0.1', 9501, -1)) {
	exit('连接服务器失败' . $client->errCode . PHP_EOL);
}

for ($i = 0; $i < 3; $i++) { 
	$client->send($i);	
}

$client->close();

运行客户端并查看服务端进程 ID 是否发生变化

$php 13-swoole-client.php 
server: 0server: 1server: 2

# 查看进程,可以看见进程 ID 已经发生变化
$pstree -p 37925
php(37925)─┬─php(37926)─┬─php(38024)
           │            └─php(38025)
           └─{php}(37927)

从这里可以看出主进程 ID 没有发生变化。当 worker 进程处理的请求数量达到规定值时自动退出,Manager 进程就会重新拉起一个新的 Worker 进程进程任务处理。

本篇文章到此结束,我是温新,下篇文章继续学习 Swoole。

请登录后再评论