记一次fastapi中,BackgroundTasks使用遇到的问题
·
问题背景大概是这样的:项目是fastapi,有一个接口有个耗时操作,我想把这个耗时操作扔到后台,所以使用了BackgroundTasks。但是并没有得到预期的结果,接口依然耗时很长,且期间使用其他接口会堵塞。
问题代码大概是这样的:
@router.get('/dialog-info')
def get_dialog_info(request: Request, background_tasks: BackgroundTasks, key: str):
ret = {
'dialog_info': {}
}
# xxx一段逻辑略过
# 使用BackgroundTasks在后台 添加操作
background_tasks.add_task(add_history,key)
return JSONResponse(get_resp_body(ret))
async def add_history(key: str):
#这里有一段sqlalchemy的数据库操作,我这简单写了下示意
with db_session_scope('xxx', 'w') as session:
session.execute(xxxxx).fetchall()
问题的原因是:session.execute(xxxxx).fetchall() 这是一个同步查询,阻塞了当前的请求,即使我是在后台任务中执行它。它也会等待数据库操作完成,导致请求无法立即响应。
再往深处看看原因:
先提一个问题:fastapi在同时接收两个请求时,会在两个线程中执行任务吗?
- 在 FastAPI 中,是否使用多个线程来处理多个请求,取决于你如何配置 FastAPI 及其环境运行,但通常情况下,是不会开两个线程的。
- FastAPI 是基于
asyncio的,默认情况下是 单线程异步。这意味着当 FastAPI 接收到多个请求时,它通常 不会为每个请求启动独立的线程。相反,FastAPI 会使用一个 事件循环(event loop),并且通过async和await关键字来非阻塞地处理多个请求。 - 在这种情况下,当 FastAPI 接收到多个请求时,它会依次调度每个请求的处理函数,但如果一个请求的处理需要等待 I/O 操作(例如数据库查询、外部 API 请求等),它会在等待期间释放控制权,允许事件循环继续处理其他请求。这使得 FastAPI 在处理 I/O 密集型任务时非常高效。
- 但是,由于事件循环是异步的,只要请求本身是非阻塞的(例如,使用异步 I/O 操作),FastAPI 就能够同时处理多个请求。
在了解了fastapi的机制后,回过头看这个问题:
- SQLAlchemy 本身并不直接支持异步操作,它的主要设计是同步的(SQLAlchemy 2.x之后可以,但要安装需要的依赖包)
- 在
add_history这个函数里,即使不是MySQL的耗时操作,是es的耗时操作,或者干脆直接用time.sleep,只要是同步阻塞的,那么就会有这个问题。
GPT给的几个解决方法:
- 启动多个进程:
gunicorn -w 4,简单粗暴的解决 - 将阻塞的数据库查询移到后台线程,避免阻塞主线程,示例如下
import asyncio
async def add_history(user: User, ad_key: str):
# 使用 asyncio.to_thread 执行阻塞的 SQL 查询
await asyncio.to_thread(run_slow_query)
def run_slow_query():
with db_session_scope('xxx', 'w') as session:
session.execute(xxx).fetchall()
- 使用支持异步的数据库,如
databases等(我也没具体实践,后面再试试)
更多推荐




所有评论(0)