资源管理是 PHP 应用中容易被忽视但至关重要的领域。PHP 脚本执行完毕后会自动清理内存,但这种自动化可能导致开发者忽略资源管理的细节,直到应用在高并发下崩溃。理解 PHP 的资源生命周期,掌握各种资源的优化策略,是构建稳定高性能应用的基础。
参考:https://xrzqr.cn/category/disaster-warning.html
内存管理:PHP 使用引用计数加写时复制管理内存。每个变量是一个 zval 结构体,包含类型、值和引用计数。当引用计数降为零时,内存被释放。循环引用(如两个对象互相引用)会导致内存泄漏,除非使用垃圾回收器。PHP 的垃圾回收器定期扫描可能产生循环引用的变量,将其回收。
内存泄漏的常见来源包括:循环引用(特别是在长生命周期对象中)、全局变量累积(尤其是在 Swoole 等常驻内存环境中)、静态数组无限增长、以及未关闭的资源。在传统 PHP-FPM 中,内存泄漏在请求结束后被清除,影响有限;在 Swoole 中,内存泄漏会累积直到进程重启。
文件句柄:每个 fopen 调用都应该有对应的 fclose。未关闭的文件句柄会占用系统资源,最终导致“打开文件过多”错误。使用 finally 块或 RAII 风格的包装器可以确保资源释放。现代框架的文件系统抽象层通常自动管理句柄生命周期。
数据库连接:数据库连接是昂贵的资源。每次建立连接需要 TCP 握手、认证、设置会话变量等。连接池复用现有连接,避免重复建立的开销。PHP-FPM 传统上不支持跨请求连接复用,但可以配置持久连接(需要谨慎,可能导致连接数爆炸)。Swoole 和 RoadRunner 内置连接池支持,可以显著减少连接开销。
参考:https://rvxif.cn/category/white-tea.html
Redis 连接:与数据库类似,Redis 连接池可以减少网络往返。Redis 是单线程的,大量短连接会增加服务端负担。使用长连接和连接池,配合 Pipeline 批量操作,可以大幅提升性能。
文件锁:当多个进程访问同一文件时(如日志文件、缓存文件),需要使用文件锁避免数据损坏。flock 函数提供共享锁和排他锁,但需要注意阻塞和非阻塞模式。在 Swoole 协程环境中,文件锁会阻塞整个进程,应避免使用,改用 Redis 分布式锁或数据库锁。
内存缓存:APCu 是用户数据共享内存缓存,适合存储计算昂贵的结果(如配置解析、路由缓存)。APCu 的数据跨请求共享,但仅在单个服务器内。对于多服务器集群,使用 Redis 或 Memcached。
临时文件:处理大文件上传或生成大报表时,临时文件是必要的。tmpfile 创建临时文件,关闭后自动删除。tempnam 生成临时文件名,需要手动删除。sys_get_temp_dir 返回系统临时目录。临时文件应该在使用后立即删除,避免填满磁盘。
资源泄漏检测:xdebug_debug_zval 显示变量的引用计数,帮助追踪循环引用。memory_get_usage 监控内存使用。get_resources 列出当前打开的资源。lsof 命令查看进程打开的文件描述符。在生产环境,可以使用 Blackfire 或 Tideways 进行资源监控。
参考:https://bgnno.cn/category/maintenance.html
PHP-FPM 的进程管理:每个 PHP-FPM 进程处理完请求后不会立即释放所有内存,而是保留部分内存池以备重用。这提高了性能,但也意味着长期运行的进程可能积累内存碎片。pm.max_requests 配置项强制进程处理一定请求后退出,避免内存无限增长。
Swoole 的特殊考虑:在常驻内存环境中,全局变量和静态属性跨越请求持久化,需要手动清理。defer 回调在请求结束时执行,用于释放临时资源。Co\run 为协程创建沙箱环境。内存泄漏在 Swoole 中尤其危险,建议定期重启工作进程。
容器环境的资源限制:在 Docker/Kubernetes 中运行 PHP 时,需要正确配置内存限制。PHP 的 memory_limit 应该小于容器的内存限制,否则 OOM Killer 会终止容器。CPU 限制影响 PHP-FPM 的 pm.max_children 计算——每个进程消耗一个 CPU 核心的时间片,过多进程会导致频繁上下文切换。
资源管理的核心原则是:谁创建,谁释放。使用 RAII 风格的包装器(如智能指针的等价物)自动管理生命周期。在异常处理中确保资源释放。定期监控资源使用趋势,在达到阈值前采取措施。资源管理不是 PHP 的强项,但通过细心设计,PHP 应用可以高效稳定地运行。
参考:https://rvxif.cn