FastAPI异步编程:高性能API服务的秘密
前言
FastAPI的异步特性是其高性能的核心。本文将深入讲解FastAPI异步编程的原理和实践,帮助你构建高并发的API服务。
适合读者: 后端开发者、Python工程师、架构师
一、同步vs异步
1.1 同步阻塞的问题
# ❌ 同步代码 - 阻塞
import time
import requests
def get_user(user_id):
# 阻塞1秒
response = requests.get(f"https://api.example.com/users/{user_id}")
return response.json()
def get_multiple_users():
users = []
for i in range(10):
user = get_user(i) # 每次阻塞1秒
users.append(user)
return users # 总共需要10秒
# 性能:10个请求 = 10秒
1.2 异步非阻塞的优势
# ✅ 异步代码 - 非阻塞
import asyncio
import httpx
async def get_user(user_id):
async with httpx.AsyncClient() as client:
response = await client.get(f"https://api.example.com/users/{user_id}")
return response.json()
async def get_multiple_users():
tasks = [get_user(i) for i in range(10)]
users = await asyncio.gather(*tasks) # 并发执行
return users # 总共需要1秒
# 性能:10个请求 = 1秒(并发)
二、FastAPI异步基础
2.1 异步路由
from fastapi import FastAPI
import httpx
app = FastAPI()
# 同步路由
@app.get("/sync/users/{user_id}")
def get_user_sync(user_id: int):
# 阻塞操作
response = requests.get(f"https://api.example.com/users/{user_id}")
return response.json()
# 异步路由
@app.get("/async/users/{user_id}")
async def get_user_async(user_id: int):
# 非阻塞操作
async with httpx.AsyncClient() as client:
response = await client.get(f"https://api.example.com/users/{user_id}")
return response.json()
2.2 何时使用异步
# ✅ 应该使用异步的场景
@app.get("/data")
async def get_data():
# 1. 数据库查询
result = await db.execute(query)
# 2. HTTP请求
async with httpx.AsyncClient() as client:
response = await client.get(url)
# 3. 文件I/O
async with aiofiles.open('file.txt', 'r') as f:
content = await f.read()
# 4. 缓存操作
value = await redis.get(key)
return result
# ❌ 不需要异步的场景
@app.get("/calculate")
def calculate():
# CPU密集型计算
result = sum(range(1000000))
return {"result": result}
三、异步数据库操作
3.1 SQLAlchemy异步配置
# database.py
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.ext.asyncio import async_sessionmaker
from sqlalchemy.orm import declarative_base
DATABASE_URL = "postgresql+asyncpg://user:password@localhost/dbname"
# 创建异步引擎
engine = create_async_engine(
DATABASE_URL,
echo=True,
pool_size=20,
max_overflow=0
)
# 创建异步会话工厂
async_session = async_sessionmaker(
engine,
class_=AsyncSession,
expire_on_commit=False
)
Base = declarative_base()
# 依赖注入
async def get_db() -> AsyncSession:
async with async_session() as session:
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raise
3.2 异步CRUD操作
# crud/user.py
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from models import User
from schemas import UserCreate, UserUpdate
async def create_user(db: AsyncSession, user: UserCreate) -> User:
"""创建用户"""
db_user = User(**user.dict())
db.add(db_user)
await db.flush()
await db.refresh(db_user)
return db_user
async def get_user(db: AsyncSession, user_id: int) -> User | None:
"""获取用户"""
result = await db.execute(
select(User).where(User.id == user_id)
)
return result.scalar_one_or_none()
async def get_users(
db: AsyncSession,
skip: int = 0,
limit: int = 100
) -> list[User]:
"""获取用户列表"""
result = await db.execute(
select(User).offset(skip).limit(limit)
)
return result.scalars().all()
async def update_user(
db: AsyncSession,
user_id: int,
user_update: UserUpdate
) -> User | None:
"""更新用户"""
db_user = await get_user(db, user_id)
if not db_user:
return None
for field, value in user_update.dict(exclude_unset=True).items():
setattr(db_user, field, value)
await db.flush()
await db.refresh(db_user)
return db_user
async def delete_user(db: AsyncSession, user_id: int) -> bool:
"""删除用户"""
db_user = await get_user(db, user_id)
if not db_user:
return False
await db.delete(db_user)
return True
3.3 API路由实现
# api/users.py
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from database import get_db
from crud import user as crud_user
from schemas import User, UserCreate, UserUpdate
router = APIRouter(prefix="/api/users", tags=["users"])
@router.post("", response_model=User)
async def create_user(
user: UserCreate,
db: AsyncSession = Depends(get_db)
):
"""创建用户"""
return await crud_user.create_user(db, user)
@router.get("/{user_id}", response_model=User)
async def get_user(
user_id: int,
db: AsyncSession = Depends(get_db)
):
"""获取用户"""
db_user = await crud_user.get_user(db, user_id)
if not db_user:
raise HTTPException(status_code=404, detail="用户不存在")
return db_user
@router.get("", response_model=list[User])
async def get_users(
skip: int = 0,
limit: int = 100,
db: AsyncSession = Depends(get_db)
):
"""获取用户列表"""
return await crud_user.get_users(db, skip, limit)
@router.put("/{user_id}", response_model=User)
async def update_user(
user_id: int,
user_update: UserUpdate,
db: AsyncSession = Depends(get_db)
):
"""更新用户"""
db_user = await crud_user.update_user(db, user_id, user_update)
if not db_user:
raise HTTPException(status_code=404, detail="用户不存在")
return db_user
@router.delete("/{user_id}")
async def delete_user(
user_id: int,
db: AsyncSession = Depends(get_db)
):
"""删除用户"""
success = await crud_user.delete_user(db, user_id)
if not success:
raise HTTPException(status_code=404, detail="用户不存在")
return {"msg": "删除成功"}
四、异步HTTP请求
4.1 使用httpx
import httpx
from fastapi import APIRouter
router = APIRouter()
@router.get("/proxy/users/{user_id}")
async def proxy_user(user_id: int):
"""代理请求到第三方API"""
async with httpx.AsyncClient() as client:
response = await client.get(
f"https://api.example.com/users/{user_id}",
timeout=10.0
)
return response.json()
@router.post("/batch/users")
async def batch_get_users(user_ids: list[int]):
"""批量获取用户(并发)"""
async with httpx.AsyncClient() as client:
tasks = [
client.get(f"https://api.example.com/users/{uid}")
for uid in user_ids
]
responses = await asyncio.gather(*tasks)
return [r.json() for r in responses]
4.2 错误处理
@router.get("/safe/users/{user_id}")
async def safe_proxy_user(user_id: int):
"""带错误处理的代理请求"""
try:
async with httpx.AsyncClient() as client:
response = await client.get(
f"https://api.example.com/users/{user_id}",
timeout=10.0
)
response.raise_for_status()
return response.json()
except httpx.TimeoutException:
raise HTTPException(status_code=504, detail="请求超时")
except httpx.HTTPStatusError as e:
raise HTTPException(
status_code=e.response.status_code,
detail=f"上游服务错误: {e}"
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
五、异步Redis操作
5.1 Redis配置
# cache.py
import redis.asyncio as redis
from typing import Optional
class RedisCache:
def __init__(self, url: str = "redis://localhost:6379/0"):
self.redis = redis.from_url(url, decode_responses=True)
async def get(self, key: str) -> Optional[str]:
"""获取缓存"""
return await self.redis.get(key)
async def set(
self,
key: str,
value: str,
expire: int = 3600
) -> bool:
"""设置缓存"""
return await self.redis.set(key, value, ex=expire)
async def delete(self, key: str) -> bool:
"""删除缓存"""
return await self.redis.delete(key) > 0
async def exists(self, key: str) -> bool:
"""检查键是否存在"""
return await self.redis.exists(key) > 0
async def close(self):
"""关闭连接"""
await self.redis.close()
# 全局实例
cache = RedisCache()
5.2 缓存装饰器
import json
import functools
from typing import Callable
def cached(expire: int = 3600):
"""缓存装饰器"""
def decorator(func: Callable):
@functools.wraps(func)
async def wrapper(*args, **kwargs):
# 生成缓存键
cache_key = f"{func.__name__}:{args}:{kwargs}"
# 尝试从缓存获取
cached_value = await cache.get(cache_key)
if cached_value:
return json.loads(cached_value)
# 执行函数
result = await func(*args, **kwargs)
# 存入缓存
await cache.set(cache_key, json.dumps(result), expire)
return result
return wrapper
return decorator
# 使用示例
@router.get("/cached/users/{user_id}")
@cached(expire=300) # 缓存5分钟
async def get_cached_user(user_id: int):
# 这个函数的结果会被缓存
async with httpx.AsyncClient() as client:
response = await client.get(f"https://api.example.com/users/{user_id}")
return response.json()
六、后台任务
6.1 BackgroundTasks
from fastapi import BackgroundTasks
import asyncio
async def send_email(email: str, message: str):
"""发送邮件(模拟耗时操作)"""
await asyncio.sleep(2) # 模拟发送邮件
print(f"邮件已发送到 {email}: {message}")
@router.post("/register")
async def register_user(
user: UserCreate,
background_tasks: BackgroundTasks,
db: AsyncSession = Depends(get_db)
):
"""注册用户并发送欢迎邮件"""
# 创建用户
db_user = await crud_user.create_user(db, user)
# 添加后台任务
background_tasks.add_task(
send_email,
user.email,
"欢迎注册!"
)
return db_user
6.2 Celery集成
# tasks.py
from celery import Celery
celery_app = Celery(
"tasks",
broker="redis://localhost:6379/0",
backend="redis://localhost:6379/0"
)
@celery_app.task
def process_data(data: dict):
"""处理数据(CPU密集型任务)"""
# 复杂的数据处理
result = heavy_computation(data)
return result
# API中使用
@router.post("/process")
async def process_data_endpoint(data: dict):
"""提交数据处理任务"""
task = process_data.delay(data)
return {"task_id": task.id}
@router.get("/task/{task_id}")
async def get_task_status(task_id: str):
"""查询任务状态"""
task = celery_app.AsyncResult(task_id)
return {
"task_id": task_id,
"status": task.status,
"result": task.result if task.ready() else None
}
七、并发控制
7.1 信号量限流
import asyncio
# 限制并发数
semaphore = asyncio.Semaphore(10)
@router.get("/limited/users/{user_id}")
async def limited_get_user(user_id: int):
"""限制并发的请求"""
async with semaphore:
async with httpx.AsyncClient() as client:
response = await client.get(
f"https://api.example.com/users/{user_id}"
)
return response.json()
7.2 超时控制
@router.get("/timeout/users/{user_id}")
async def timeout_get_user(user_id: int):
"""带超时控制的请求"""
try:
async with asyncio.timeout(5.0): # 5秒超时
async with httpx.AsyncClient() as client:
response = await client.get(
f"https://api.example.com/users/{user_id}"
)
return response.json()
except asyncio.TimeoutError:
raise HTTPException(status_code=504, detail="请求超时")
八、性能优化
8.1 连接池配置
# 数据库连接池
engine = create_async_engine(
DATABASE_URL,
pool_size=20, # 连接池大小
max_overflow=10, # 最大溢出连接数
pool_timeout=30, # 获取连接超时
pool_recycle=3600, # 连接回收时间
)
# HTTP客户端连接池
http_client = httpx.AsyncClient(
limits=httpx.Limits(
max_connections=100,
max_keepalive_connections=20
)
)
8.2 批量操作
@router.post("/batch/create")
async def batch_create_users(
users: list[UserCreate],
db: AsyncSession = Depends(get_db)
):
"""批量创建用户"""
db_users = [User(**user.dict()) for user in users]
db.add_all(db_users)
await db.flush()
for db_user in db_users:
await db.refresh(db_user)
return db_users
九、错误处理
9.1 全局异常处理
from fastapi import Request
from fastapi.responses import JSONResponse
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
"""全局异常处理"""
return JSONResponse(
status_code=500,
content={
"code": 500,
"msg": "服务器内部错误",
"detail": str(exc)
}
)
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
"""HTTP异常处理"""
return JSONResponse(
status_code=exc.status_code,
content={
"code": exc.status_code,
"msg": exc.detail
}
)
十、测试
10.1 异步测试
# test_api.py
import pytest
from httpx import AsyncClient
from main import app
@pytest.mark.asyncio
async def test_create_user():
"""测试创建用户"""
async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.post(
"/api/users",
json={
"username": "test",
"email": "test@example.com",
"password": "password123"
}
)
assert response.status_code == 200
data = response.json()
assert data["username"] == "test"
@pytest.mark.asyncio
async def test_get_user():
"""测试获取用户"""
async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.get("/api/users/1")
assert response.status_code == 200
十一、总结
FastAPI异步编程的核心要点:
✅ 异步路由 - 使用async/await
✅ 异步数据库 - SQLAlchemy异步ORM
✅ 异步HTTP - httpx并发请求
✅ 异步缓存 - Redis异步操作
✅ 并发控制 - 信号量和超时
下一篇预告: 《SQLAlchemy 2.0异步ORM实战指南》
作者简介: 资深开发者,创业者。专注于视频通讯技术领域。国内首本Flutter著作《Flutter技术入门与实战》作者,另著有《Dart语言实战》及《WebRTC音视频开发》等书籍。多年从事视频会议、远程教育等技术研发,对于Android、iOS以及跨平台开发技术有比较深入的研究和应用,作为主要程序员开发了多个应用项目,涉及医疗、交通、银行等领域。
学习资料:
欢迎交流: 如有问题欢迎在评论区讨论 🚀