FastAPI跨服务高频互请求导致微服务冻结问题求助
双向互调FastAPI微服务高负载冻结问题解决思路
问题背景
用FastAPI搭建两个双向互调的微服务:服务A向服务B发起"How are you?"请求,B回复"Good!";反之服务B也会向A发起同样请求,请求按设定延迟循环执行。两个服务核心代码一致,仅启动配置不同。
服务核心代码
from fastapi import FastAPI import requests import uvicorn import asyncio app = FastAPI() def get(url, headers, json_data=False): if json_data: r = requests.get(url, headers=headers, json=json_data) else: r = requests.get(url, headers=headers) return r @app.get("/") async def how_are_you(): message = f"{service_name}: Good!" print(message) return message async def toc_toc(message): await asyncio.sleep(5) url = f"http://{remote_host}:{remote_port}/" headers = { 'accept': 'application/json', } count = 0 while True: await asyncio.sleep(waiting) print(f"{count}\t{service_name}: {message}!") get(url, headers) count += 1 async def run_webserver(): config = uvicorn.Config(app, host=host, port=port) server = uvicorn.Server(config) await server.serve() async def main(): await asyncio.gather( toc_toc("How are you"), run_webserver() )
服务1启动配置
if __name__ == "__main__": waiting = 0.1 service_name = "service_1" host = "0.0.0.0" port = 82 remote_host = "0.0.0.0" remote_port = 83 loop = asyncio.get_event_loop() tasks = main() loop.run_until_complete(tasks)
服务2启动配置
if __name__ == "__main__": waiting = 0.1 service_name = "service_2" host = "0.0.0.0" port = 83 remote_host = "0.0.0.0" remote_port = 82 loop = asyncio.get_event_loop() tasks = main() loop.run_until_complete(tasks)
问题现象
- 当
waiting延迟设为1秒时,服务运行正常; - 延迟降至0.1秒或更低时,双向互调数百次后服务无报错冻结;
- 单向请求(仅一个服务发起请求)时,即使延迟设为0.001秒也能正常运行;
- 问题在Mac M1本地环境和本地Kubernetes集群中均复现,需要支持0.001秒延迟以模拟生产负载峰值。
解决思路
1. 替换同步HTTP客户端为异步实现
问题根源:requests是同步阻塞库,在asyncio事件循环中调用会阻塞整个线程。双向高频率互调时,大量阻塞请求会占用事件循环的所有资源,导致服务无法处理新请求或自身的异步任务,最终冻结。
解决方案:改用httpx.AsyncClient,它支持异步请求,不会阻塞事件循环。修改请求函数为异步版本,并在调用时添加await关键字。
2. 限制并发请求数
问题根源:双向互调会形成请求闭环,高频率下请求量会指数级增长,超出服务的处理能力,导致资源耗尽。
解决方案:使用asyncio.Semaphore限制并发请求的最大数量,避免瞬间产生过多请求压垮服务。
3. 优化事件循环性能
问题根源:默认的asyncio事件循环在高负载下性能有限,尤其是ARM架构的Mac M1设备或K8s环境中,可能无法应对高频请求。
解决方案:使用uvloop作为事件循环,它是基于libuv的高性能asyncio事件循环实现,能显著提升高负载下的处理能力。
修改后的核心代码示例
from fastapi import FastAPI import httpx import uvicorn import asyncio from asyncio import Semaphore app = FastAPI() # 限制最大并发请求数,可根据实际情况调整 request_semaphore = Semaphore(100) async def get(url, headers, json_data=False): async with request_semaphore: async with httpx.AsyncClient() as client: if json_data: response = await client.get(url, headers=headers, json=json_data) else: response = await client.get(url, headers=headers) return response @app.get("/") async def how_are_you(): message = f"{service_name}: Good!" print(message) return message async def toc_toc(message): await asyncio.sleep(5) url = f"http://{remote_host}:{remote_port}/" headers = { 'accept': 'application/json', } count = 0 while True: await asyncio.sleep(waiting) print(f"{count}\t{service_name}: {message}!") await get(url, headers) count += 1 async def run_webserver(): config = uvicorn.Config(app, host=host, port=port) server = uvicorn.Server(config) await server.serve() async def main(): # 尝试使用uvloop提升性能 try: import uvloop asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) except ImportError: # 若uvloop未安装,使用默认事件循环 pass await asyncio.gather( toc_toc("How are you"), run_webserver() )
额外说明
- 安装依赖:执行
pip install httpx uvloop以获取所需的异步库; - 调整并发数:
Semaphore的数值可根据服务器性能和实际负载需求调整,避免过大或过小; - 监控请求状态:可添加请求超时设置(如
httpx.AsyncClient(timeout=5)),防止单个请求阻塞过久。
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

