swoole 第6章 常驻内存以及如何避免内存泄漏 swoole 第6章 常驻内存以及如何避免内存泄漏

2021-06-09

一、内存开销

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 未请求前我们看下当前的进程结构

https://file.lulublog.cn/images/3/2022/07/dtJtccJVAbTt98TAmjVE9djv8Y8qVC.png

注意进程 id 等于 15644 和 15645 哦,这两个一个是 Worker 进程,一个是 Task 进程。Mac 下我们就不区分到底谁是谁了。

随后我们让客户端请求 3 次,再看下结果

https://file.lulublog.cn/images/3/2022/07/rvr66ppzI4PPp0RptcA01QeecPpVdr.png

有没有发现原先进程 id 等于 15645 的现在变成 15680 了?请求 3 次后我们确定是 Worker 进程自动退出了,并且 Manager 进程拉起了一个 15680 的 Worker 进程。

我们再请求一次,第四次

https://file.lulublog.cn/images/3/2022/07/fjmo2xjOBhBpjO1h2i29882hjxbO4p.png

发现进程 id 等于 15644 的 Task 进程消失了,有一个新的子进程 15704 被重新创建了。

看来官方没有骗人,说的都对。

So…原来我在一开始介绍的那么多都是废话?

不全是,因为 max_request 参数对 server 有下面几种限制条件。

  • max_request 只能用于同步阻塞、无状态的请求响应式服务器程序

  • 纯异步的 Server 不应当设置 max_request

  • 使用 Base 模式时 max_request 是无效的,其中 Base 模式是 swoole 运行模式的一种,

  • 我们主要介绍多进程模式

五、总结

  • 常驻内存减少了不小开销,swoole 不错

  • 应尽量避免使用全局变量,不用最好,没啥用

  • max_request 可以解决 php 的内存溢出问题,但是主要还是要养成释放内存的习惯,

  • 因为 max_request 也有限制场景

打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,你说多少就多少

打开微信扫一扫,即可进行扫码打赏哦

阅读 786