FastAPI如何在顺序处理请求时保持服务响应性?
问题描述
我需要给服务器提供一个处理GPU密集型渲染任务的API,该任务同一时间仅能处理一个请求。客户端提交任务后应立即收到201 ACCEPTED响应,任务处理时长可达1分钟,可能存在数十个待处理请求。
我编写了最小可复现示例代码:
import time import asyncio from fastapi import FastAPI, status app = FastAPI() fifo_queue = asyncio.Queue() async def process_requests(): while True: name = await fifo_queue.get() # 等待队列中的请求 print(name) time.sleep(10) # 阻塞线程的资源密集型任务 fifo_queue.task_done() # 标记请求处理完成 @app.on_event("startup") async def startup_event(): asyncio.create_task(process_requests()) # 启动请求处理任务 @app.get("/render") async def render(name): fifo_queue.put_nowait(name) # 将请求参数加入队列 return status.HTTP_201_CREATED # 返回201状态码
当前方案存在问题:服务器无法保持响应性,处理第一个请求后便持续忙碌,无法正常响应后续请求。
示例中仅需将time.sleep(10)替换为await asyncio.sleep(10)即可解决问题,但实际场景中此方法不适用。请问有什么可行的解决思路?
可行解决思路
1. 用线程池执行阻塞任务
把GPU密集型的阻塞任务放到线程池中运行,避免卡住asyncio的事件循环。Python 3.9+自带asyncio.to_thread()直接使用,也可以手动用concurrent.futures.ThreadPoolExecutor管理:
修改后的process_requests函数:
async def process_requests(): while True: name = await fifo_queue.get() print(name) # 把阻塞任务丢去线程池执行 await asyncio.to_thread(run_gpu_render, name) fifo_queue.task_done() def run_gpu_render(name): # 这里写实际的GPU渲染代码 time.sleep(10) # 模拟阻塞任务
2. 用进程池处理计算密集型任务
如果GPU任务是纯计算密集型,线程池受GIL限制效率不高,就用进程池。注意任务参数要能序列化,还要确保子进程能正确访问GPU资源:
from concurrent.futures import ProcessPoolExecutor # 限制只开一个进程,保证同一时间处理一个任务 executor = ProcessPoolExecutor(max_workers=1) async def process_requests(): while True: name = await fifo_queue.get() print(name) loop = asyncio.get_running_loop() # 提交任务到进程池 await loop.run_in_executor(executor, run_gpu_render, name) fifo_queue.task_done() def run_gpu_render(name): time.sleep(10) # 模拟GPU渲染任务
3. 拆分任务到独立服务
把渲染任务完全剥离成独立进程或服务,比如用Celery配合Redis/RabbitMQ做任务队列:
- FastAPI只负责接请求,把任务参数丢进消息队列,直接返回201
- 单独跑一个worker进程(只开一个实例)监听队列,执行GPU渲染
- 还可以加个查询任务状态的API,让客户端查进度
这种方式解耦彻底,适合长期跑大量任务的场景,后续要加GPU并行也方便扩展worker数量。
4. 用asyncio子进程执行外部渲染命令
如果渲染任务已经封装成命令行工具,就用asyncio.create_subprocess_exec()启动子进程,天然不阻塞事件循环:
async def process_requests(): while True: name = await fifo_queue.get() print(name) # 启动子进程运行渲染命令 proc = await asyncio.create_subprocess_exec( "your-render-cli", name, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) await proc.wait() # 等子进程跑完 fifo_queue.task_done()
内容的提问来源于stack exchange,提问作者Lukasz Tracewski
相关产品推荐
相关产品推荐

