当前位置:首页 > 文章列表 > 文章 > python教程 > Python FastAPI 实战:别把耗时任务塞进请求生命周期

Python FastAPI 实战:别把耗时任务塞进请求生命周期

来源:Python 博主原创 2026-06-04 13:36:17 0浏览 收藏

FastAPI 写 demo 很快,真正上生产以后,麻烦通常不是路由不会写,而是请求生命周期被塞得太满:启动时临时创建连接池,接口里直接跑阻塞导出,响应后用 BackgroundTasks 改关键订单状态,最后 P99 飙高、任务丢失、关闭进程还卡住。

这篇文章不做 FastAPI 入门,也不复述官方文档。我按一次真实后端排障来写:一个 Python API 服务从“偶尔慢、偶尔丢后台任务”开始,怎么把应用级资源放进 lifespan,把阻塞调用隔离到线程池,把关键任务移出请求生命周期,并给外部调用加上超时和可观测性。

Python FastAPI 生产请求治理思维导图
治理思路:lifespan 管资源,依赖注入管清理,后台任务只做轻量副作用,阻塞调用必须隔离。

业务场景:订单导出接口拖垮了整个服务

一个订单系统有个导出接口,产品要求点一下就生成 Excel,并把导出结果通知到用户。最初代码看着很直白:接口里查订单、生成文件、调内部通知服务,最后顺手把审计日志丢到后台任务。

import time
from fastapi import BackgroundTasks, FastAPI

app = FastAPI()

def write_audit_log(order_id: str) -> None:
    time.sleep(2)
    print(f"audit {order_id}")

@app.get("/orders/{order_id}/export")
async def export_order(order_id: str, background_tasks: BackgroundTasks):
    rows = query_orders_from_db(order_id)      # 同步阻塞
    file_url = build_excel(rows)               # CPU/文件阻塞
    notify_user(order_id, file_url)            # 同步 HTTP 调用
    background_tasks.add_task(write_audit_log, order_id)
    return {"file_url": file_url}

低流量时它能跑;并发一上来,现象就很熟悉:接口偶尔超过网关超时,其他无关接口也变慢,发布重启时后台任务没跑完,审计日志有缺口。问题不在 FastAPI 慢,而是我们把太多不可控工作塞进了一次请求里。

先画清请求边界

FastAPI 的优势是异步友好,但 async def 不是免死金牌。只要你在异步接口里直接执行阻塞函数,事件循环就会被卡住。同步数据库驱动、同步 HTTP 客户端、大文件生成、压缩、复杂 CPU 运算,都需要单独处理。

Python FastAPI 请求生命周期流程图
请求链路:启动阶段建资源池,请求阶段只做必要工作,后台阶段只放可丢、可重试的小副作用。

我排查 FastAPI 慢请求时会先把链路拆成四块:应用启动/关闭、请求进入/依赖注入、业务处理/外部调用、响应之后的副作用。拆完以后,哪些工作该放进请求内,哪些必须移出去,就清楚很多。

第一刀:用 lifespan 管应用级资源

连接池、HTTP 客户端、模型句柄、缓存客户端这类对象,不应该在每次请求里临时创建,也不应该靠模块 import 时偷偷初始化。更稳的做法是在应用 lifespan 里创建,在关闭时释放。

from contextlib import asynccontextmanager

import httpx
from fastapi import FastAPI, Request

@asynccontextmanager
async def lifespan(app: FastAPI):
    app.state.notify_client = httpx.AsyncClient(
        base_url="https://notify.internal",
        timeout=httpx.Timeout(2.0, connect=0.5),
    )
    yield
    await app.state.notify_client.aclose()

app = FastAPI(lifespan=lifespan)

def notify_client(request: Request) -> httpx.AsyncClient:
    return request.app.state.notify_client

这个结构有两个好处:启动失败会尽早暴露,关闭时资源能正常释放。线上服务最怕“请求里第一次用才发现连接池建不起来”,也怕滚动发布时旧进程带着未关闭连接慢慢退出。

第二刀:异步接口里不要直接跑阻塞函数

如果依赖还没换成异步版本,至少先把阻塞函数隔离到线程池。Starlette 提供的 run_in_threadpool 是 FastAPI 项目里常见的兜底手段,但它不是无限资源,仍然要配合限流、超时和监控。

from starlette.concurrency import run_in_threadpool

@app.get("/orders/{order_id}/export")
async def export_order(order_id: str):
    rows = await run_in_threadpool(query_orders_from_db, order_id)
    file_url = await run_in_threadpool(build_excel, rows)
    return {"file_url": file_url}

我通常把这一步当过渡方案:先止血,让事件循环不被卡死;下一步再评估是否替换异步数据库驱动、拆分导出任务,或者把大文件生成挪到独立 worker。

第三刀:BackgroundTasks 不是任务队列

BackgroundTasks 的定位很实用:响应返回后做一点小副作用,比如写访问日志、轻量通知、清理临时文件。但它不适合承载关键业务状态变更。进程被杀、任务异常、实例重启、发布滚动,都可能让你失去可追踪性。

from fastapi import BackgroundTasks

def cleanup_tmp_file(path: str) -> None:
    remove_file_if_exists(path)

@app.post("/reports")
async def create_report(background_tasks: BackgroundTasks):
    path = await run_in_threadpool(render_small_report)
    background_tasks.add_task(cleanup_tmp_file, path)
    return {"path": path}

如果任务影响订单状态、扣款、发货、结算、重要通知,我会把它投递到有持久化和重试能力的队列里。Celery、RQ、Arq 都可以,具体选型另说;关键原则是:关键任务要能记录、重试、告警、补偿,不能只挂在一次 HTTP 响应后面。

Python FastAPI 阻塞调用和后台任务边界代码对照
代码审查重点:异步接口里直接阻塞、后台任务改关键状态,都是生产事故的常见入口。

第四刀:所有外部调用都要有超时

Python API 服务慢下来,很多时候不是自己代码慢,而是等下游等到天荒地老。外部 HTTP、RPC、数据库、缓存都要有明确 timeout。没有 timeout 的调用,本质上是在把你的 worker 借给别人无限使用。

from typing import Annotated

import httpx
from fastapi import Depends, HTTPException, Request

async def get_notify_client(request: Request) -> httpx.AsyncClient:
    return request.app.state.notify_client

@app.post("/orders/{order_id}/notify")
async def notify_order(
    order_id: str,
    client: Annotated[httpx.AsyncClient, Depends(get_notify_client)],
):
    try:
        resp = await client.post("/messages", json={"order_id": order_id})
        resp.raise_for_status()
    except httpx.TimeoutException as exc:
        raise HTTPException(status_code=504, detail="notify timeout") from exc
    return {"ok": True}

这里我没有把异常吞掉。线上排障时,超时、连接失败、下游 5xx 要能被日志和指标看见。否则你看到的只是“业务偶尔没通知”,而不是“notify 服务 13:20 到 13:24 P99 飙高”。

诊断步骤:从事件循环被阻塞开始查

如果线上 FastAPI 服务出现“一个慢接口拖慢所有接口”,我会按这个顺序查:

  • 打开 access log 和慢请求日志,按 path、P95、P99 排序。
  • 检查慢接口里是否有同步 I/O、time.sleep、大文件处理、CPU 密集循环。
  • 检查 BackgroundTasks 是否承担关键业务动作,失败是否可追踪。
  • 检查 HTTP 客户端、数据库连接池是否在请求内重复创建。
  • 检查所有下游调用是否有 timeout,异常是否进入日志、指标和告警。
  • 用压测复现:一个慢导出接口并发打满时,健康检查和普通查询是否被拖慢。

上线检查清单

  • 应用级资源统一放进 lifespan,关闭阶段释放连接池和客户端。
  • async def 接口里禁止直接调用阻塞函数;临时方案用线程池隔离。
  • 线程池隔离要配合限流,不把大批 CPU/文件任务无限塞进去。
  • BackgroundTasks 只做轻量、非关键、可容忍失败的副作用。
  • 关键任务进入持久化队列,具备重试、幂等、告警和补偿。
  • 所有外部调用都有 connect/read timeout,异常能被日志和指标捕获。
  • 压测必须包含慢接口和普通接口混合流量,观察是否互相拖累。

总结

FastAPI 的生产稳定性,不是把所有函数改成 async def 就结束了。真正要治理的是请求生命周期:启动时准备资源,请求中只做必要工作,阻塞调用隔离,响应后的后台任务守住边界,外部调用用 timeout 收口。

我的经验是:越是“顺手”的代码,越容易成为线上事故入口。顺手创建客户端、顺手跑同步导出、顺手把关键状态丢到后台任务,短期看省事,长期看都会在高并发和发布重启时还回来。把边界画清楚,FastAPI 才能既快又稳。

版本声明
本文转载于:Python 博主原创 如有侵犯,请联系study_golang@163.com删除
Go HTTP 客户端超时实战:别让默认 Client 拖垮 goroutineGo HTTP 客户端超时实战:别让默认 Client 拖垮 goroutine
上一篇
Go HTTP 客户端超时实战:别让默认 Client 拖垮 goroutine
Python Django 实战:async view 里别直接摸同步 ORM
下一篇
Python Django 实战:async view 里别直接摸同步 ORM
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • ChatExcel酷表:告别Excel难题,北大团队AI助手助您轻松处理数据
    ChatExcel酷表
    ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
    5963次使用
  • Any绘本:开源免费AI绘本创作工具深度解析
    Any绘本
    探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
    6383次使用
  • 可赞AI:AI驱动办公可视化智能工具,一键高效生成文档图表脑图
    可赞AI
    可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
    6193次使用
  • 星月写作:AI网文创作神器,助力爆款小说速成
    星月写作
    星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
    8167次使用
  • MagicLight.ai:叙事驱动AI动画视频创作平台 | 高效生成专业级故事动画
    MagicLight
    MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
    6773次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码