uWSGI + Django 稳定性 CPU 抖动问题排查记录

问题现象

在稳定性测试中,一个使用 uWSGI 发布的 Django 应用出现了周期性 CPU 抖动。从监控图表可以看出,每大约 4 小时出现一次明显的 CPU 使用峰值,同时伴随内存下降:

uWSGI CPU 周期性抖动监控图

初步分析:归因于 GC 的误区

第一直觉:垃圾回收(GC)

  • 观察现象:CPU 峰值时内存下降,看起来像是 GC 触发
  • 时间规律:每约 4 小时一次,呈强周期性
  • 排查方向:查找是否有定时 GC(未发现)

怀疑方向:所有 worker 同时 GC

通过检索得知 uWSGI 的 pre-fork 模式可能导致:

  1. 所有 worker 共享相同 GC 配置
  2. 因为处理请求节奏类似,会同步触发 GC
  3. 大规模同时 GC → CPU 突刺

实施与验证

尝试方案 1:随机化 GC 阈值

import random
import gc
from uwsgidecorators import postfork

@postfork
def randomize_gc_threshold():
    """每个 worker 设置不同的 GC 阈值,避免同时 GC"""
    gc.set_threshold(
        random.randint(700, 900),  # generation 0
        random.randint(8, 12),     # generation 1
        random.randint(8, 12)      # generation 2
    )

结果:CPU 抖动未改善。

尝试方案 2:专用 GC 线程

原计划创建独立线程执行周期性 GC,但在进一步检索中发现关键线索……

关键突破:重新审视 uWSGI 配置

多处资料推荐如下配置:

max-requests = 5000
max-requests-delta = 300

然而实际项目配置为:

max-requests = 50000  # 处理 50000 个请求后重启 worker

计算验证

根据测试环境 TPS(约 3.5 req/s)计算:

重启间隔 = 50000 / 3 ≈ 16666.7 秒 ≈ 4.6 小时

完美吻合监控图中的 4 小时左右的 CPU 峰值!

CPU 抖动根本不是 GC,而是 worker 重启周期

真相大白:worker 周期重启导致 CPU 峰值

工作机制

  1. worker 处理 max-requests 数量后触发优雅重启
  2. 重启过程中会:
    • 释放 Python 解释器内存
    • 重新加载 Django 应用
    • 重建连接池
    • 重新导入模块

内存下降原因

不是 GC,而是:

  1. 老 worker 退出
  2. OS 完整回收该进程内存
  3. 新 worker 启动重新分配内存

优化建议

找到了问题点,那么解决起来就好说了,按照GPT给的说法,如果我们将重启点分散,那么这个CPU的凸起会有改善。于是用了一下调整测试了

调整重启策略(当前采用)

max-requests = 10000
max-requests-delta = 300

优点

  • 避免单 worker 堆积大量状态导致重启成本过大
  • 随机偏移避免同时重启
  • CPU 与内存曲线更平稳

基于内存的重启触发

reload-on-rss = 512  # RSS > 512MB 则重启
reload-on-as = 768   # 虚拟空间 > 768MB 则重启

再次出现的问题

测试后并未看到明显“随机效果”。开始怀疑 max-requests-delta 是否被支持。

执行验证:

uwsgi --help | grep max-requests-delta

未找到该参数。

进一步搜索

uwsgi --help | grep delta

输出为:

--max-worker-lifetime-delta  add (worker_id * delta) seconds to the max_worker_lifetime value of each worker

说明当前版本仅支持:

--max-worker-lifetime-delta

并且该机制是 按 worker_id 线性偏移,而非随机偏移

经验教训总结

排查误区反思

  1. 现象归因偏差
    • 内存下降 ≠ GC
    • 周期性 ≠ 定时任务
    • 必须考虑所有相关机制
  2. 配置忽视
    • 过度关注代码
    • 忽略 runtime 配置
    • 中间件的生命周期管理影响深远
  3. AI 给出的答案需要验证
    • 多模型交叉校验
    • 核对官方文档
    • 版本差异非常关键

配置审计清单

uWSGI 关键配置:

  • max-requests
  • max-worker-lifetime
  • reload-on-rss
  • harakiri
  • enable-metrics

后续

uWSGI 的重启机制是 CPU 抖动根源,但真正 CPU 占用高的地方是 Django 应用加载过程。 后续可进一步减少 worker 数量或优化 Django 启动速度。

核心收获

  1. 基础设施配置等同于代码质量的重要性
  2. 理解中间件生命周期是排障关键
  3. 建立系统化排查思维模型
  4. 监控结合日志才是完整视角

这次排查提醒我们:在复杂系统中,看似显而易见的原因往往只是错觉。真正的调优来自理解每一层组件的运行机制,而不是停留在表象。