FastAPI 生命周期事件详解

举报
时光不写 发表于 2026/07/21 16:18:53 2026/07/21
【摘要】 详解 FastAPI 中两种生命周期管理方式——传统 on_event 装饰器与现代 lifespan上下文管理器,包括数据库连接池初始化、资源加载、优雅关闭等实战场景与最佳实践。

在 Web 应用的生命周期中,「启动时初始化」和「关闭时清理」是两个绕不开的需求:数据库连接池需要在服务启动时创建、在关闭时释放;机器学习模型需要在启动时加载到内存;缓存需要在关闭时持久化到磁盘。FastAPI(底层 Starlette)提供了两套生命周期机制:传统的 @app.on_event 装饰器与现代的 lifespan 上下文管理器。本文带你彻底搞懂这两种方式的核心原理与最佳实践。

为什么需要生命周期管理

一个典型的 FastAPI 应用启动后就开始接收请求,但很多资源无法在请求级别按需创建——比如数据库连接池,如果每个请求都新建再销毁,性能会极差;又比如一份 2GB 的 ML 模型,不可能每次推理时重新加载。

生命周期事件要解决的核心问题是:把昂贵的初始化逻辑放在服务启动时执行一次,把资源释放逻辑放在服务关闭时执行一次,让所有请求共享这些预热的资源。

传统方式:on_event 装饰器

FastAPI 早期(Starlette 0.x 时代)使用 on_event 装饰器来注册启动和关闭事件:

from fastapi import FastAPI

app = FastAPI()


@app.on_event("startup")
async def startup_db():
    """服务启动时:初始化数据库连接池"""
    import asyncpg
    app.state.db_pool = await asyncpg.create_pool(
        dsn="postgresql://user:pass@localhost/mydb",
        min_size=5,
        max_size=20,
    )
    print("数据库连接池已创建")


@app.on_event("shutdown")
async def shutdown_db():
    """服务关闭时:释放数据库连接池"""
    await app.state.db_pool.close()
    print("数据库连接池已关闭")


@app.get("/users")
async def list_users():
    async with app.state.db_pool.acquire() as conn:
        rows = await conn.fetch("SELECT id, name FROM users LIMIT 10")
    return [dict(r) for r in rows]

关键机制

  1. app.state 是跨请求共享状态的推荐载体——它是 Starlette 提供的全局状态字典,生命周期覆盖整个应用实例;
  2. 所有 startup 事件按注册顺序依次执行,所有 shutdown 事件按注册逆序执行;
  3. 事件函数可以是同步或异步的;
  4. 多个 on_event("startup") 可以分别注册,互不干扰。

on_event 的不足

虽然简单直观,但 on_event 有两个明显缺陷:

  • 资源与事件之间没有显式的生命周期绑定关系:如果 startup 中创建了连接池,你需要手动在 shutdown 中关闭它——两者在代码层面没有强制关联,容易遗漏;
  • 无法优雅地处理 startup 阶段的异常:一个 startup 失败,已注册的其他 startup 可能不会正确回滚已初始化的部分。

现代方式:lifespan 上下文管理器

Starlette 从 0.26 版本开始推荐使用 lifespan 上下文管理器,FastAPI 在 0.93+ 版本中正式支持。它通过 Python 原生的 async with 语法,在结构上天然地将初始化与清理绑定在一起:

from contextlib import asynccontextmanager
from fastapi import FastAPI


@asynccontextmanager
async def lifespan(app: FastAPI):
    # ===== 启动阶段:yield 之前的所有代码 =====
    import asyncpg
    app.state.db_pool = await asyncpg.create_pool(
        dsn="postgresql://user:pass@localhost/mydb",
        min_size=5,
        max_size=20,
    )
    print("数据库连接池已创建 (lifespan)")

    yield  # ← 服务运行期间停在这里

    # ===== 关闭阶段:yield 之后的所有代码 =====
    await app.state.db_pool.close()
    print("数据库连接池已关闭 (lifespan)")


app = FastAPI(lifespan=lifespan)

与 on_event 的核心区别

特性 on_event lifespan
资源生命周期绑定 分散在多个函数,靠约定 包裹在同一函数中,语法级绑定
异常处理 startup 异常可能导致部分资源泄漏 yield 前抛异常会自动跳过关闭逻辑,结构更安全
代码组织 多个装饰器分散在各处 集中管理所有初始化/清理逻辑
推荐版本 旧项目兼容保留 当前推荐方式

注意lifespanon_event 不能混用——如果传入了 lifespan 参数,on_event 装饰器注册的事件将被忽略。

更多实战场景

场景一:加载机器学习模型

from contextlib import asynccontextmanager
from fastapi import FastAPI

@asynccontextmanager
async def lifespan(app: FastAPI):
    # 启动时加载模型(耗时操作只执行一次)
    from transformers import pipeline
    app.state.classifier = pipeline("sentiment-analysis")
    app.state.summarizer = pipeline("summarization")
    print("模型加载完成")

    yield

    # 关闭时释放 GPU 显存
    del app.state.classifier
    del app.state.summarizer
    print("模型资源已释放")


app = FastAPI(lifespan=lifespan)


@app.post("/analyze")
async def analyze(text: str):
    result = app.state.classifier(text)
    return {"sentiment": result[0]}

场景二:缓存预热 + 磁盘持久化

import json
from contextlib import asynccontextmanager
from fastapi import FastAPI

CACHE_FILE = "hot_cache.json"


@asynccontextmanager
async def lifespan(app: FastAPI):
    # 启动:从磁盘加载热缓存
    try:
        with open(CACHE_FILE) as f:
            app.state.cache = json.load(f)
    except FileNotFoundError:
        app.state.cache = {}
    print(f"缓存已加载,{len(app.state.cache)} 条记录")

    yield

    # 关闭:持久化当前缓存到磁盘
    with open(CACHE_FILE, "w") as f:
        json.dump(app.state.cache, f, ensure_ascii=False, indent=2)
    print("缓存已持久化")


app = FastAPI(lifespan=lifespan)


@app.get("/cache/{key}")
async def get_cache(key: str):
    return {"value": app.state.cache.get(key)}

场景三:HTTP 客户端会话管理

import httpx
from contextlib import asynccontextmanager
from fastapi import FastAPI


@asynccontextmanager
async def lifespan(app: FastAPI):
    # 创建全局复用的异步 HTTP 客户端
    async with httpx.AsyncClient(
        timeout=httpx.Timeout(30.0),
        limits=httpx.Limits(max_connections=50),
    ) as client:
        app.state.http_client = client
        yield  # 服务运行期间使用这个 client
    # async with 退出时自动关闭


app = FastAPI(lifespan=lifespan)


@app.get("/proxy")
async def proxy_external_api():
    resp = await app.state.http_client.get("https://api.example.com/data")
    return resp.json()

这里使用 httpx.AsyncClientasync with 自带生命周期管理,嵌套在 lifespan 中实现双层保障。

注意事项

  1. 不要在 lifespan 中执行耗时过长的阻塞操作——这会导致服务启动慢、健康检查失败。如果初始化确实很慢(如下载模型),考虑使用后台线程或延迟加载策略;
  2. yield 之后的清理代码一定会执行,即使服务收到 SIGTERM 信号——Starlette 会等待清理完成后退出,这是优雅关闭的保障;
  3. 如果 lifespan 中抛出了未捕获异常,FastAPI 会终止启动过程,应用不会开始接收请求;
  4. 避免在 lifespan 中访问请求级别的依赖(如 Request 对象),这些在应用生命周期事件中不可用。

迁移建议

如果你的项目还在使用 @app.on_event,建议尽快迁移到 lifespan

# 迁移前(旧版)
@app.on_event("startup")
async def init(): ...

@app.on_event("shutdown")
async def cleanup(): ...

# 迁移后(推荐)
@asynccontextmanager
async def lifespan(app: FastAPI):
    # 将 init 逻辑放在这里
    ...
    yield
    # 将 cleanup 逻辑放在这里
    ...

app = FastAPI(lifespan=lifespan)

迁移过程通常是机械的——把 startup 代码移到 yield 之前,shutdown 代码移到 yield 之后即可。

总结

要点 说明
推荐方式 lifespan 上下文管理器(FastAPI 0.93+)
共享状态容器 app.state
执行顺序 startup → 接收请求 → shutdown(逆序清理)
适用场景 数据库连接池、模型加载、缓存预热、HTTP 客户端复用
混用禁忌 lifespanon_event 不可共存

掌握生命周期事件是构建「生产可用」FastAPI 应用的关键一步——它让你能优雅地管理全局资源,用最少的代码实现健壮的服务启动与关闭。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。