使用Gunicorn Gevent Worker时Requests请求延迟过高的问题排查
Gevent Worker下Requests请求耗时异常问题排查
问题背景
我在对外提供HTTP接口的Flask应用中使用Gunicorn的Gevent Worker,接口执行流程中会调用另一个HTTP接口获取数据,且复用同一个requests.session。相关代码及测试数据如下:
rest_client.py代码
import requests import json import os import time class RestClient: base_url = None session = None def __init__(self): if not RestClient.session: RestClient.base_url = f"http://localhost:5000" RestClient.session = requests.session() def fetch_http(self, key: str, name: str) -> str: fetch_http_endpoint = f"{self.base_url}/client/get-result" query_params = {"key": key, "name": name} session_get_start_time = time.time() * 1000 response = self.session.get(fetch_http_endpoint, params=query_params) session_get_end_time = time.time() * 1000 session_get_total_time = session_get_end_time - session_get_start_time print(f"session.get total time {session_get_total_time}") if response.status_code == 200: parsed_json = json.loads(response.content) result = parsed_json["result"] return result
gunicorn.py配置
PORT = os.getenv("PORT", 5000) RELOAD = os.getenv("DEBUG", False) bind = f"0.0.0.0:{PORT}" workers = 1 # Only run one worker at a time, we deploy in k8s worker_class = "gevent" # Async workers reload = RELOAD
测试耗时数据
Gevent Worker下session.get耗时:
session.get total time 28710.727783203125 session.get total time 23681.550048828125 session.get total time 18187.310302734375 session.get total time 21550.77294921875 session.get total time 20483.166259765625 session.get total time 10297.47705078125 session.get total time 5146.256103515625
同步Worker下session.get耗时:
session.get total time 41.621826171875 session.get total time 35.197998046875 session.get total time 24.538818359375 session.get total time 25.270751953125 session.get total time 21.31787109375 session.get total time 27.0400390625
版本信息
python==3.8 requests==2.24.0 gevent==21.1.2 gunicorn==20.1.0
我尝试过在导入Requests前显式猴子补丁但无效,Datadog Profiler显示热点在gevent/hub.py第166行的sleep函数,推测事件循环被阻塞,现寻求以下帮助:
- 使用Gevent Worker时出现该问题的原因是什么?
- 是否存在Requests未被猴子补丁的情况?如何验证?
- 为何同步Worker在该Demo中表现更优?
问题解答
1. Gevent Worker下请求耗时异常的原因
核心原因是Gevent事件循环被阻塞,结合热点日志分析,具体可能是:
- 全局共享的
RestClient.session在协程环境中存在竞态冲突,导致协程间出现无意义的等待; - 猴子补丁不完整,Requests底层的socket操作未被替换为Gevent的异步实现,同步IO直接阻塞了整个事件循环;
- 单Gevent Worker处理并发请求时,阻塞IO会让所有协程排队等待,表现为请求耗时急剧上升。
2. 验证Requests是否被正确猴子补丁
可以通过以下方式验证:
- 检查socket模块归属:在应用启动代码中添加打印,确认socket被Gevent替换:
import socket print(socket.socket) # 输出包含`gevent._socket3.socket`则说明补丁生效 - 检查urllib3的socket实现:Requests依赖urllib3,查看其HTTPConnection使用的socket:
from urllib3.connection import HTTPConnection print(HTTPConnection.socket) # 需指向Gevent的socket实现 - 强制前置补丁并验证:在应用入口最顶部执行补丁(确保在导入requests、socket前):
再运行上述检查代码,确认补丁生效状态。from gevent import monkey monkey.patch_all()
3. 同步Worker表现更优的原因
同步Worker(默认sync worker)采用请求-线程/进程模型,每个请求的IO操作相互独立,不会互相阻塞。而你的测试场景中,单Gevent Worker补丁未生效时,所有并发请求的IO都是同步阻塞的,相当于单线程串行处理,一旦有请求触发IO等待,后续请求全部排队,导致总耗时飙升。同步Worker的线程模型下,每个请求的IO等待不影响其他请求,因此耗时保持正常水平。
内容的提问来源于stack exchange,提问作者ravi katiyar
相关产品推荐
相关产品推荐

