FastAPI应用生命周期:startup与shutdown事件全攻略
你是否曾遇到过FastAPI应用启动时需要初始化数据库连接,却不知如何优雅实现?服务关闭时未正确释放资源导致内存泄漏?本文将系统讲解FastAPI应用生命周期管理,通过具体代码示例和最佳实践,帮你彻底掌握startup与shutdown事件的使用技巧。读完本文你将学会:
- 两种生命周期管理方案的实现方式
- 全局资源的安全创建与释放
- 状态管理的最佳实践
- 生命周期事件的测试策略
认识FastAPI生命周期
FastAPI应用从启动到关闭的完整过程称为应用生命周期(Lifecycle),包含三个关键阶段:启动准备(Startup)、运行中(Runtime)和关闭清理(Shutdown)。通过生命周期事件,我们可以在应用启动时初始化数据库连接池、缓存服务等资源,在关闭时优雅释放这些资源,避免数据丢失或连接泄漏。
传统Flask/Django应用通常通过装饰器注册启动和关闭函数,而FastAPI提供了两种更现代的实现方式:基于装饰器的简化方案和基于上下文管理器的高级方案。后者作为ASGI规范的一部分,提供了更强大的状态管理能力。
快速上手:装饰器方案
对于简单场景,FastAPI提供了on_event装饰器,可以轻松注册启动和关闭事件处理函数。这种方式适合中小型应用或需要快速实现的场景。
from fastapi import FastAPI
from redis import asyncio as aioredis
app = FastAPI()
redis = None
@app.on_event("startup")
async def startup_event():
global redis
# 启动时初始化Redis连接
redis = await aioredis.from_url("redis://localhost")
await redis.set("app_status", "running")
@app.on_event("shutdown")
async def shutdown_event():
# 关闭时释放Redis连接
await redis.close()
print("Redis connection closed")
@app.get("/status")
async def get_status():
status = await redis.get("app_status")
return {"status": status.decode()}
上述代码实现了Redis连接的自动管理:应用启动时创建连接(startup_event),接收请求时可直接使用全局变量redis操作数据库,应用关闭时自动释放连接(shutdown_event)。这种方式的优点是简单直观,缺点是使用全局变量可能导致状态管理混乱,尤其在复杂应用中。
高级方案:Lifespan上下文管理器
FastAPI 0.93.0版本引入了基于ASGI规范的lifespan参数,提供了更强大的生命周期管理能力。通过上下文管理器模式,我们可以将资源创建、使用和释放的逻辑封装在一起,同时支持类型注解和状态共享,避免了全局变量的使用。
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from typing import TypedDict
from fastapi import FastAPI, Request
from redis import asyncio as aioredis
# 定义状态类型,支持类型检查
class AppState(TypedDict):
redis: aioredis.Redis
request_count: int
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[AppState]:
# 启动阶段:创建资源
redis = await aioredis.from_url("redis://localhost")
await redis.set("app_status", "starting")
# 共享状态
state: AppState = {
"redis": redis,
"request_count": 0
}
yield state # 应用运行阶段
# 关闭阶段:释放资源
await redis.set("app_status", "stopped")
await redis.close()
print("Cleanup completed")
# 初始化应用时指定生命周期
app = FastAPI(lifespan=lifespan)
@app.get("/status")
async def get_status(request: Request):
# 访问共享状态
state: AppState = request.state
state["request_count"] += 1
status = await state["redis"].get("app_status")
return {
"status": status.decode(),
"request_count": state["request_count"]
}
这种方案的核心优势在于:
- 类型安全:通过
TypedDict定义状态结构,支持IDE自动补全和类型检查 - 状态隔离:避免全局变量,状态通过
request.state安全传递 - 资源管理:上下文管理器确保资源无论正常退出还是异常终止都能正确释放
生命周期事件的实际应用场景
1. 数据库连接池管理
在生产环境中,数据库连接池的管理至关重要。通过生命周期事件,我们可以在应用启动时创建连接池,避免每次请求都新建连接带来的性能损耗。
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[dict]:
# 创建数据库连接池
engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/db")
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
yield {"db_session": async_session}
# 关闭连接池
await engine.dispose()
app = FastAPI(lifespan=lifespan)
@app.get("/users")
async def get_users(request: Request):
async with request.state.db_session() as session:
result = await session.execute("SELECT id, name FROM users LIMIT 10")
return result.mappings().all()
2. 配置加载与验证
应用启动时加载配置并验证,确保配置正确后再开始处理请求:
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
import os
from pydantic_settings import BaseSettings
class AppSettings(BaseSettings):
api_key: str
max_requests: int = 1000
class Config:
env_file = ".env"
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[dict]:
# 加载并验证配置
settings = AppSettings()
# 验证必要配置
if not settings.api_key:
raise ValueError("API_KEY is not set in environment variables")
yield {"settings": settings}
app = FastAPI(lifespan=lifespan)
@app.get("/config")
async def get_config(request: Request):
return {
"max_requests": request.state.settings.max_requests
}
生命周期事件的测试策略
为确保生命周期事件正确工作,需要进行专门测试。推荐使用pytest-asyncio配合httpx.AsyncClient,结合asgi-lifespan库模拟生命周期环境。
import pytest
from asgi_lifespan import LifespanManager
from httpx import AsyncClient
from main import app
@pytest.mark.asyncio
async def test_lifespan_events():
async with LifespanManager(app):
async with AsyncClient(app=app, base_url="http://test") as client:
# 测试应用状态
response = await client.get("/status")
assert response.status_code == 200
assert response.json()["status"] == "running"
# 测试请求计数
await client.get("/status")
await client.get("/status")
response = await client.get("/status")
assert response.json()["request_count"] == 3
注意:测试环境中应使用测试数据库和配置,避免影响生产数据。可以通过环境变量区分不同环境的配置。
最佳实践与注意事项
1. 优先使用Lifespan上下文管理器
虽然装饰器方案简单,但Lifespan上下文管理器提供了更好的资源隔离和类型支持。根据README.md中的建议,app.state已不推荐使用,应优先采用lifespan状态管理。
2. 避免阻塞操作
启动和关闭事件中应避免长时间阻塞操作,特别是同步IO操作。如果必须执行耗时操作,建议使用后台任务:
@app.on_event("startup")
async def startup_event():
# 启动后台任务处理耗时操作
asyncio.create_task(initialize_heavy_resources())
3. 异常处理
在生命周期事件中适当处理异常,确保资源正确释放:
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[dict]:
resource = None
try:
resource = await create_resource()
yield {"resource": resource}
except Exception as e:
print(f"Error during startup: {e}")
raise # 确保异常传播,阻止应用启动
finally:
if resource:
await resource.close()
总结与展望
FastAPI的生命周期管理机制为应用资源管理提供了优雅解决方案,特别是基于上下文管理器的lifespan方案,兼顾了灵活性和类型安全性。通过合理使用生命周期事件,我们可以实现:
- 资源的自动创建与释放
- 应用状态的集中管理
- 配置的加载与验证
- 应用健康检查与监控
随着FastAPI的不断发展,生命周期管理功能也在持续完善。未来可能会看到更多高级特性,如生命周期钩子的优先级控制、更细粒度的状态管理等。掌握本文介绍的生命周期管理技巧,将帮助你构建更健壮、可维护的FastAPI应用。
建议收藏本文作为参考,并关注项目README.md获取更多FastAPI使用技巧。你在应用生命周期管理中有什么经验或问题?欢迎在评论区分享讨论!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



