Skip to content

11分钟宕机教训:Webman 内存泄露排查与性能优化实战

一、案例背景:长假后的 11 分钟生产事故

  • 某日早晨,公司基于 Webman 开发的核心 API 服务突发宕机,服务器响应中断,最终导致业务停摆 11 分钟。

  • 异常表现: An image

    • 内存持续攀升: 该服务启动初期内存占用约为 40%,运行 2 天后会增长至 80%。

    • 运维压力: 由于内存无法自动释放,此前必须每 2 天手动重启服务。

    • 触发原因: 本次事故正值节假日后,由于长假期间缺乏人工干预,内存最终耗尽导致系统崩溃。

  • 反思: 依靠手动重启维持的服务架构,在长假面前极度脆弱。

二、 深度排查:定位内存增长的“元凶”

参考 Webman 官方文档关于内存泄露的说明,我们按优先级进行了逐一排查:

1. 代码层面检测

首先排查了 PHP 常驻内存中最常见的泄露点:

  • 无限膨胀的static数组

  • 无限膨胀的单例数组属性

  • 无限膨胀的global数组

结果: 经过代码审计,并未发现上述明显的编程错误。

2. 运行机制分析

排除代码 bug 后,我们将目光转向 PHP 的内存分配机制。在处理大文件上传、大批量数据库查询或解析超大 Excel 时,PHP 会申请大量内存。

  • 机制痛点: 请求结束后 Webman 虽然释放了逻辑层面的内存,但 PHP 解释器为了后续复用性能,并不会立即将内存还给 OS。

  • 监控失效: Webman 自带的 monitor 进程监控由于 memory_limit 配置过大(512M),导致在触发进程重启阈值前,服务器物理内存已先被耗尽。

3. 架构耦合问题

进一步排查发现,该服务将 API 接口、定时任务(Crontab) 与 队列消费者(Redis-queue) 混合在同一进程池中启动。复杂的常驻脚本进一步加剧了内存管理的难度。

三、 优化策略:服务拆分与精准配额

为了彻底解决此问题,我们实施了**“动静分离”与“精细化配置”**的优化方案:

1. 引入“慢服务”隔离机制

针对不同复杂度的接口进行资源隔离:

  • 高耗能接口(慢服务): 将大内存占用、长响应时间的接口剥离到独立服务,配置较低的进程数和适中的 memory_limit (256M),防止单接口拖垮全局。

  • 常规接口(轻服务): 保持高并发进程数,设置较小的 memory_limit (64M)。一旦某个进程处理异常请求导致内存上涨,能迅速触发 monitor 的重启机制。

2. 业务脚本彻底剥离

将定时任务和队列消费脚本从 Web 服务中剥离,改由 CLI 独立启动:

  • 稳定性: API 发版重启不再导致后台任务中断。
  • 解耦: 脚本与 Web 服务互不干扰,可根据业务负载独立扩展。

四、 优化效果

经过一周的观察,优化效果显著:

  • 内存基准: 启动初始内存占用从 40% 降至 35%。

  • 长期稳定性: 连续运行 5 天后,内存始终稳定在 40% 左右的健康水平,无需再进行人工干预。

An image