Flask服务导入requests库后停滞超时无响应问题咨询
场景说明
使用Python Flask服务对外提供远程数据库的数据访问服务,计划使用requests框架建立持久连接对接数据库。
使用技术栈
- Python 3.10
- Flask 2.0.3
- Poetry 1.0(依赖管理工具)
- Requests 2.28.1(通过Poetry添加依赖)
- IBM Code Engine(云平台/运行环境)
异常现象
项目原本运行正常,当在server.py顶部添加import requests代码行后,服务无法返回任何响应,请求直接超时。导入其他部分框架时也会出现同类异常。
可复现代码
未添加
import requests时可正常返回{ "hello": "there" }:
import os import sys import json from collections import Counter from flask import Flask, render_template, request, session, url_for, jsonify app = Flask(__name__) @app.route('/', methods=['POST', 'GET']) def main(): return { 'hello': "there" } if __name__ == 'main': app.run()
添加
import requests后服务卡顿无任何响应:
import os import sys import json import requests # 新增导入行 from collections import Counter from flask import Flask, render_template, request, session, url_for, jsonify app = Flask(__name__) @app.route('/', methods=['POST', 'GET']) def main(): return { 'hello': "there" } if __name__ == 'main': app.run()
故障原因
核心是prefork工作模式下的锁状态复制异常,和业务逻辑无关:
- IBM Code Engine默认使用
gunicorn作为WSGI服务器运行Flask应用,默认采用prefork工作模式:master进程先完成所有模块级代码的加载(包括顶层写的import requests),再fork出多个worker进程处理实际请求。 requests依赖的urllib3在导入阶段就会初始化连接池相关的线程锁,fork操作会把父进程中已经初始化的锁状态原封不动复制到子进程,直接导致子进程内的锁处于不一致的错误状态。- 当请求进入worker进程,只要触发urllib3相关的初始化逻辑(哪怕没有显式调用requests的接口,部分依赖的自检逻辑也会触发),就会永久阻塞在锁等待上,对外表现为所有请求超时无响应。
- 导入其他涉及网络连接、线程锁初始化的第三方框架时出现同类问题,触发逻辑完全一致。
另外代码中存在两个不影响本次故障、但会导致本地/自定义启动失败的错误:
- 启动判断书写错误:
if __name__ == 'main'正确写法为if __name__ == '__main__'(main两侧是双下划线) - 本地启动时没有指定监听地址和端口,不符合IBM Code Engine要求应用监听
0.0.0.0:8080的规则。
修复方案
三选一即可,优先选择前两种生产可用方案:
方案1:配置gunicorn使用lazy加载模式
修改IBM Code Engine的启动命令为:
gunicorn --bind 0.0.0.0:8080 --workers 2 --lazy-apps server:app
--lazy-apps参数会让每个worker进程独立加载应用代码,而非master进程加载完再fork,从根源避免锁状态复制的问题,不需要修改任何业务代码。
方案2:延迟导入requests
不要在模块顶层导入requests,把导入逻辑放到实际使用的视图函数/工具函数内部,确保导入动作在worker进程启动完成后执行:
import os import sys import json from collections import Counter from flask import Flask, render_template, request, session, url_for, jsonify app = Flask(__name__) @app.route('/', methods=['POST', 'GET']) def main(): # 函数内部导入,worker启动完成后才执行 import requests return { 'hello': "there" } # 修正本地启动逻辑 if __name__ == '__main__': app.run(host='0.0.0.0', port=8080, debug=False)
方案3:更换gunicorn的工作模式
把gunicorn的worker从默认的sync模式换成gevent/eventlet异步模式,这类模式不会走prefork的fork逻辑,也不会出现锁复制问题,启动命令示例:
gunicorn --bind 0.0.0.0:8080 --workers 2 --worker-class gevent server:app
注意使用该方案需要提前通过Poetry安装gevent依赖。
内容的提问来源于stack exchange,提问作者Brandon A
相关产品推荐
相关产品推荐

