Azure虚拟机上FastAPI调用AWS Bedrock闲置后超时问题
Azure VM上FastAPI调用AWS Bedrock闲置后首次请求超时问题解决方案
问题概述
在Azure虚拟机部署的FastAPI应用中,通过langchain-aws的ChatBedrockConverse调用AWS Bedrock服务时,闲置数分钟后的首次请求会触发botocore.exceptions.ReadTimeoutError(达到默认60秒超时限制),后续请求可立即成功,直到下一次闲置期。已验证:
- 相同容器化应用在AWS EC2实例上运行无此问题
- 同一Azure VM上,使用AWS CLI、直接基于botocore的Python脚本调用Bedrock均正常,仅长期运行的FastAPI/Uvicorn进程内出现该问题
错误栈:
File "/app/.venv/lib/python3.13/site-packages/botocore/endpoint.py", line 383, in _send return self.http_session.send(request) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^ File "/app/.venv/lib/python3.13/site-packages/botocore/httpsession.py", line 501, in send raise ReadTimeoutError(endpoint_url=request.url, error=e) botocore.exceptions.ReadTimeoutError: Read timeout on endpoint URL: "https://bedrock-runtime.us-east-1.amazonaws.com/model/us.anthropic.claude-3-5-sonnet-20241022-v2%3A0/converse"
问题根源
问题出在长期运行的Uvicorn进程中,botocore维护的HTTP连接池内的闲置连接被Azure网络或AWS端主动关闭,但客户端未检测到连接失效,复用了无效连接。当应用闲置时,TCP连接长时间无数据传输,触发网络层超时断开(如Azure负载均衡或AWS端节点的闲置超时机制),但botocore连接池仍认为该连接可用,首次请求复用无效连接时就会超时;后续请求会创建新的有效连接,因此恢复正常。
解决方案
1. 配置连接池闲置超时,主动回收旧连接
修改LangChainBedrockClient中的botocore.Config,添加闲置连接超时设置,让客户端主动关闭超过阈值的闲置连接,避免复用无效连接:
class LangChainBedrockClient: def __init__(self, region='us-east-1', timeout=60): self.region = region self.timeout = timeout self.model_id = 'us.anthropic.claude-3-5-sonnet-20241022-v2:0' self.config = Config( region_name=region, read_timeout=timeout, connect_timeout=timeout, retries={'max_attempts': 5, 'mode': 'adaptive'}, max_pool_connections=10, tcp_keepalive=True, # 设置闲置连接超时为5分钟(300秒),小于Azure/AWS的默认闲置断开时间 idle_connection_timeout=300 ) self.llm = ChatBedrockConverse(model=self.model_id, region_name=region, config=self.config, temperature=0.1, max_tokens=4096, top_p=0.9) logger.info(f"Initialized LangChain BedrockConverse client with model: {self.model_id}")
2. 禁用连接复用(低并发场景适用)
如果应用并发量不高,可直接禁用连接复用,每次请求创建新连接:
class LangChainBedrockClient: def __init__(self, region='us-east-1', timeout=60): self.region = region self.timeout = timeout self.model_id = 'us.anthropic.claude-3-5-sonnet-20241022-v2:0' self.config = Config( region_name=region, read_timeout=timeout, connect_timeout=timeout, retries={'max_attempts': 5, 'mode': 'adaptive'}, # 禁用连接池复用 pool_maxsize=1, pool_connections=1, keepalive=False ) self.llm = ChatBedrockConverse(model=self.model_id, region_name=region, config=self.config, temperature=0.1, max_tokens=4096, top_p=0.9) logger.info(f"Initialized LangChain BedrockConverse client with model: {self.model_id}")
3. 实现请求前连接健康检查
自定义botocore的HTTP会话,在超时后自动清空连接池并重试:
import boto3 from botocore.httpsession import URLLib3Session from botocore.exceptions import ReadTimeoutError class HealthCheckedSession(URLLib3Session): def send(self, request): try: return super().send(request) except ReadTimeoutError: # 超时后清空连接池,下次请求创建新连接 self._adapter.clear() return super().send(request) class LangChainBedrockClient: def __init__(self, region='us-east-1', timeout=60): self.region = region self.timeout = timeout self.model_id = 'us.anthropic.claude-3-5-sonnet-20241022-v2:0' self.config = Config( region_name=region, read_timeout=timeout, connect_timeout=timeout, retries={'max_attempts': 5, 'mode': 'adaptive'} ) # 使用自定义会话创建Bedrock客户端 session = boto3.Session(region_name=region) bedrock_runtime = session.client('bedrock-runtime', config=self.config) bedrock_runtime._endpoint.http_session = HealthCheckedSession() self.llm = ChatBedrockConverse(model=self.model_id, client=bedrock_runtime, temperature=0.1, max_tokens=4096, top_p=0.9) logger.info(f"Initialized LangChain BedrockConverse client with model: {self.model_id}")
4. 定期预热连接,避免闲置断开
利用现有/warmup端点,添加定时线程定期发送测试请求,保持连接池活跃:
import threading import time def periodic_warmup(): while True: logger.info("Performing periodic warmup to keep connections alive") # 发送简单测试请求 bedrock_client.send_message(message="warmup") # 每隔4分钟执行一次(小于闲置超时时间) time.sleep(240) # 应用启动时启动预热线程 if __name__ == "__main__": import sys if len(sys.argv) > 1 and sys.argv[1] == 'client': simulate_requests() else: warmup_thread = threading.Thread(target=periodic_warmup, daemon=True) warmup_thread.start() print("To run the FastAPI server, use the command:\nuvicorn main:app --reload --port 8004") print("\nTo run the client simulation in a separate terminal, use the command:\npython main.py client")
验证建议
- 部署修改后的应用,闲置5分钟以上发起首次请求,检查是否仍超时
- 观察后续请求的响应时间,确认恢复正常
- 查看日志中的连接相关信息,验证连接池配置是否生效
内容的提问来源于stack exchange,提问作者Surya
相关产品推荐
相关产品推荐

