← 返回题目列表

FastAPI 的 lifespan、startup 和 shutdown 有什么作用?

高频 中等 第 4 / 27 题 更新于 2026/07/27
lifespanstartupshutdown生命周期

简化版

FastAPI 的生命周期钩子用于在应用启动和关闭时初始化或释放资源,比如连接池、模型、缓存客户端、定时任务等。新项目更推荐使用 lifespan 上下文管理器,而旧写法常见 @app.on_event("startup")@app.on_event("shutdown")

详细版

生命周期管理解决的是“应用级资源”的创建与销毁问题。

典型场景包括:

  • 启动时创建数据库连接池;
  • 启动时加载机器学习模型;
  • 启动时初始化 Redis、消息队列客户端;
  • 关闭时优雅释放连接;
  • 关闭时停止后台任务。

推荐写法:

from contextlib import asynccontextmanager

@asynccontextmanager
async def lifespan(app: FastAPI):
    app.state.redis = await create_redis()
    yield
    await app.state.redis.close()

app = FastAPI(lifespan=lifespan)

关键点:生命周期钩子管理的是应用级资源,不要把请求级资源放在全局生命周期里复用,比如每个请求独立的数据库 session。

完整版教学

一、为什么需要应用生命周期

一个 Web 应用不是只有“处理请求”这一个动作。它启动前可能要准备资源,关闭时也需要释放资源。如果没有统一生命周期管理,初始化逻辑可能散落在模块导入阶段,关闭逻辑可能完全缺失。

比如应用启动时要创建 Redis 连接池、加载配置、初始化日志、预热机器学习模型。应用关闭时要关闭连接池、停止后台协程、确保缓冲日志写完。生命周期钩子就是为这些场景准备的。

FastAPI 提供了启动和关闭阶段的入口,让资源管理更可控。

二、startup 和 shutdown 的传统写法

早期项目里常见写法是:

app = FastAPI()

@app.on_event("startup")
async def startup():
    app.state.cache = await create_cache()

@app.on_event("shutdown")
async def shutdown():
    await app.state.cache.close()

startup 在应用开始接收请求前执行,shutdown 在应用关闭时执行。app.state 是存放应用级共享对象的常见位置。

这种写法简单直观,老项目里仍然很多。但新版本 FastAPI 更推荐 lifespan 写法,因为它用一个上下文把资源创建和释放放在一起,可读性更好,也更符合现代 ASGI 生命周期管理方式。

三、lifespan 的推荐写法

lifespan 通常用 asynccontextmanager 实现:

from contextlib import asynccontextmanager
from fastapi import FastAPI

@asynccontextmanager
async def lifespan(app: FastAPI):
    app.state.model = load_model()
    app.state.redis = await create_redis_pool()
    try:
        yield
    finally:
        await app.state.redis.close()

app = FastAPI(lifespan=lifespan)

yield 前面的代码在启动阶段执行;yield 后面或 finally 中的代码在关闭阶段执行。这样资源的生命周期一眼就能看清楚。

对于复杂系统,lifespan 还可以集中处理多个资源的初始化顺序。如果某个资源初始化失败,应用可以直接启动失败,避免带着半残状态对外提供服务。

四、哪些资源适合放在 lifespan

适合放在 lifespan 的通常是“应用级、可复用、线程或协程安全”的资源。例如:

  • 数据库连接池,而不是具体某个请求的 session;
  • Redis 连接池;
  • HTTP 客户端连接池;
  • 机器学习模型对象;
  • 配置中心客户端;
  • 指标采集器;
  • 后台任务调度器。

这些对象的共同特点是:创建成本较高,不应该每个请求都创建一次;并且可以被多个请求安全使用。

请求级资源则不适合直接放在 lifespan 里,比如 SQLAlchemy 的 Session。Session 通常包含事务状态,不应该跨请求共享,应该通过依赖注入在每个请求中创建和关闭。

五、机器学习服务中的典型场景

FastAPI 常被用于模型服务。模型加载可能很慢,如果每次请求都加载模型,接口会非常慢。正确做法是在应用启动时加载一次:

@asynccontextmanager
async def lifespan(app: FastAPI):
    app.state.model = load_large_model("model.bin")
    yield
    app.state.model = None

请求处理时只读取 app.state.model

@app.post("/predict")
def predict(request: PredictIn):
    model = app.state.model
    return model.predict(request.features)

这样能避免重复初始化的巨大开销。但如果模型推理本身是 CPU/GPU 密集型,还要考虑并发控制、任务队列、批处理和超时保护。

六、常见误区与追问

资源适合放 lifespan 吗理由
Redis 连接池适合应用级复用,启动创建、关闭释放
大模型对象适合加载成本高,每请求加载会极慢
SQLAlchemy Session不适合请求级事务上下文,不能跨请求共享
当前登录用户不适合每个请求不同,应放依赖注入
启动阶段:加载配置 -> 建连接池 -> 加载模型 -> readiness=true
请求阶段:Depends 创建请求级资源 -> 路由处理 -> 释放请求级资源
关闭阶段:停止接流量 -> 等待请求结束 -> 关闭连接池和后台任务

易错点:lifespan 管应用级资源,Depends 管请求级资源,二者边界错了就容易出并发污染。

第一个误区是把所有初始化都写在模块顶层。模块导入阶段执行复杂初始化,会影响测试、命令行脚本和多进程启动,也不利于错误处理。

第二个误区是把请求级对象做成全局对象。例如把同一个数据库 session 放到 app.state,多个请求共用,会导致事务混乱和并发安全问题。

第三个误区是关闭阶段不处理资源释放。连接池、后台任务和异步客户端如果不关闭,可能在测试环境中出现资源泄漏警告,在生产环境中影响优雅退出。

  • 误区:模块导入时初始化大资源最简单。 多进程启动、测试导入、脚本复用都会触发顶层代码,错误也难以纳入应用生命周期管理。
  • 误区:把请求级 Session 放进 app.state 复用。 Session 携带事务和对象状态,跨请求共享会造成并发安全问题。
  • 误区:只写启动逻辑不写关闭逻辑。 Redis、HTTP 客户端、连接池、后台协程都需要在关闭阶段释放,否则影响优雅退出和测试稳定性。
  • 追问:lifespanstartup/shutdown 怎么选? 新项目优先 lifespan,因为创建和释放写在一个上下文里更清晰;老项目看到 on_event 要能读懂。
  • 追问:大模型服务为什么常把模型加载放 startup? 如果模型加载耗时 5 秒,每个请求加载都会把延迟放大到不可接受;启动加载一次能避免重复成本。
  • 追问:readiness 和 liveness 有什么区别? liveness 只说明进程还活着,readiness 说明依赖资源和初始化已完成,可以接入真实流量。

七、加强记忆

记 FastAPI 生命周期:启动前准备应用级资源,关闭时释放应用级资源;新写法优先 lifespan,旧项目常见 startup/shutdown。连接池、模型、客户端适合放生命周期里;每个请求独立的 session、事务和用户上下文要放依赖注入里。