psutil可快速定位高CPU的Flask进程:先用ps aux查高占用进程,再在代码中用psutil.Process().cpu_percent(interval=1)实时监控;需结合gunicorn配置优化、py-spy抓堆栈、规避隐式同步阻塞。

用 psutil 快速定位高CPU的Flask进程
Flask本身是单线程同步模型,CPU飙高通常不是框架本身导致,而是业务代码阻塞、循环失控或第三方调用卡住。先确认是不是你的Flask worker在吃CPU:ps aux --sort=-%cpu | head -10 看进程名和PID。如果看到 python app.py 或 gunicorn 进程占CPU 90%+,再进进程内部查。
在Python里加一段轻量监控代码,不用重启服务:
import psutil
import os
p = psutil.Process(os.getpid())
print(f"Current CPU %: {p.cpu_percent(interval=1)}")
放在请求处理函数开头或 before_request 里临时打点,能快速区分是全局持续占用,还是某个接口触发。注意 interval=1 是必须的,否则返回的是自进程启动以来的平均值,没意义。
检查 gunicorn 配置是否误启过多worker
很多线上Flask应用用 gunicorn 部署,但默认配置(比如 --workers 4)在单核机器上反而引发争抢,每个worker都在轮询空转。CPU高但QPS低,大概率是这个原因。
-
--workers建议设为2 * CPU核心数 + 1,但小内存机器要保守,1核就用1或2 - 禁用
--preload(尤其用了threading.local或数据库连接池时),它会让所有worker共享初始化阶段的资源,可能引发锁竞争 - 加上
--max-requests 1000 --max-requests-jitter 100防止内存泄漏长期积累影响调度
验证方式:改完配置后,用 gunicorn --config ./gunicorn.conf.py app:app 启动,再用 htop 观察worker数量和CPU分布。
用 py-spy 抓取正在运行的Flask线程堆栈
不需要改代码、不中断服务,py-spy 是排查Python CPU问题最直接的工具。它通过读取进程内存获取调用栈,对生产环境友好。
安装和使用:
pip install py-spy py-spy top --pid 12345 # 替换为你的Flask进程PID
常见线索:
- 大量堆栈停在
time.sleep()或socket.recv()—— 可能是下游HTTP请求没设超时,卡死在等待响应 - 反复出现在
json.dumps()或pandas.DataFrame.to_dict()—— 大对象序列化慢,尤其带NaN或嵌套深的结构 - 停在数据库驱动如
sqlite3.Connection.execute()—— SQL没加索引,或执行了SELECT *拉全表
注意:py-spy record -o profile.svg --pid 12345 可生成火焰图,比top更直观看出热点函数。
警惕Flask中隐式同步阻塞操作
Flask路由函数默认在主线程执行,任何没做异步封装的IO操作都会拖慢整个worker。最容易被忽略的是:
- 调用
requests.get()没设timeout=(3, 7),DNS失败或对方不响应时会卡住30秒以上 - 用
open()读大文件且没用with或yield流式读取,导致内存暴涨+IO阻塞 - 日志里写
logger.info(f"result={heavy_func()}"),而heavy_func()实际是数据库查询或计算,日志还没输出就先执行完了 - 模板渲染用
render_template("big.html", data=slow_db_query()),把查询逻辑塞进模板层
一个简单判断法:在路由函数开头加 import time; start = time.time(),结尾打点,看耗时是否集中在某一行。超过100ms的同步调用,基本都要拆出来优化。
真正难排查的是多线程环境下 threading.Lock 使用不当,或者用 concurrent.futures.ThreadPoolExecutor 但没控制 max_workers,导致线程数爆炸——这时候 py-spy 显示的不是业务函数,而是 threading.wait() 或 _queue.get(),得往并发模型上想。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











