Swoole 常驻内存以及如何避免内存泄漏

本文介绍了Swoole作为常驻内存运行的服务器程序所带来的性能优势及挑战,特别是内存管理方面的问题,包括如何避免内存泄漏及使用max_request参数来预防内存溢出。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关联阅读:https://cloud.tencent.com/developer/article/1700076          内存泄漏检测工具的使用

这篇文章参考了:https://deepmind.t-salon.cc/article/1695                   swoole的详细介绍

      在传统的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的代码简写如下:

<?php
    $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代码如下:

<?php
    $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也有限制场景
### PHP ThinkPHP 框架与 Swoole 扩展常见错误解决方案 #### 1. 修改业务代码后变更不生效 当使用 Swoole 运行 PHP 应用时,由于其常驻内存特性,修改后的业务代码不会自动更新。为了使新代码生效,必须执行 `reload` 或者 `restart` 命令来刷新工作进程[^1]。 ```bash # 使用 reload 刷新配置而不中断连接 php think swoole:reload # 完全重启服务 php think swoole:stop && php think swoole:start ``` #### 2. 上次请求残留数据影响后续请求 如果遇到 worker 进程未正常终止导致上次请求的数据仍然存在的情况,可以通过清理全局变量的方式解决问题。推荐的做法是在每次请求结束前重置 $_SERVER 数组: ```php // 推荐方案:清空 _SERVER 变量 $_SERVER = []; ``` 此操作能够有效防止上一次请求中的资源泄漏到新的请求中去[^2]。 #### 3. 路由缓存问题 对于 ThinkPHP 中出现的首次访问之后再次尝试不同模块或方法却总是返回初次响应的问题,这通常是由于路由缓存引起的。建议按照官方文档说明清除应用内的路由缓存并确认是否开启了调试模式以禁用不必要的缓存机制。 ```php // 清除路由缓存 (适用于 TP5.x 版本) php think route:clear ``` 另外还需检查是否存在自定义中间件或其他可能导致路径解析异常的因素。 #### 4. CLI 下启动 Swoole Server 的方式不当 早期版本可能存在将 Swoole server 实现逻辑放置于 ThinkPHP 控制器内部并通过命令行调用来触发服务器初始化的行为。这种做法容易引发各种兼容性和稳定性方面的问题。最佳实践是分离 HTTP/WebSocket server 初始化过程至独立脚本文件,并通过专门的服务管理工具来进行生命周期控制[^3]。 #### 5. Crontab 定时任务集成失败 针对基于 ThinkPHP 和 Swoole 构建的应用程序来说,在设置定时任务时需要注意正确注册相应的 Artisan Command 类以及确保调度计划已加入到框架的核心配置当中。具体而言就是在 `config/console.php` 文件内添加对应的任务处理器类映射关系[^4]。 ```php return [ 'commands' => [ 'task' => \app\command\Task::class, ], ]; ``` 最后提醒开发者们定期查看日志记录以便及时发现潜在隐患;同时保持对最新版 SDK 文档的关注从而掌握更多优化技巧和技术细节。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

戴国进

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值