FastAPI实战指南:从架构到性能调优打造高并发API
1. 为什么是FastAPI不是跟风而是异步红利终于轮到了Web层先说一个背景。我前两年主要用Flask写内部系统后来接手一个面向C端的聚合服务单接口要同时查用户信息、订单状态和推荐位内容Flask的同步写法在压测下惨不忍睹——4核8G的机器数据库查询只要超过30毫秒QPS直接跌到两位数。当时团队有人提议上Java有人提议用Golang重构最后我们选择在Python生态内换一条技术路线把服务改成FastAPI。这个决定到今天回头看方向是对的。FastAPI之所以能扛住高并发场景本质是因为它站在了ASGI和Pydantic两个肩膀上。ASGI异步服务器网关接口让Python应用在IO等待期间主动把控制权交回事件循环而不是像WSGI那样一个worker傻等一个请求。这意味着同样一台机器在数据库查询、外部API调用这类IO密集场景下FastAPI可以同时挂起成千上万个等待中的请求而不是为每个请求开线程或进程。再加上Pydantic v2在数据校验上做了Rust核心重写CPU密集的校验部分快得离谱。但我得先泼盆冷水FastAPI不是无脑选。如果你服务的核心逻辑全是CPU密集型计算比如图像处理、加解密、大规模数值计算异步也救不了你因为GIL还在事件循环也会被长计算卡住这时候还不如老老实实上多进程。FastAPI的红利集中在IO密集型API也就是现代Web服务90%以上的真实场景读数据库、调下游接口、操作Redis。理解了这个边界后面所有的优化动作才会落在正确的位置上。这篇文章我会按照自己实际搭建生产级API的顺序来写从工程目录、数据库层、数据模型到CORS与中间件、性能压测和踩坑记录全程用可直接落地的代码和配置说话。适合正在用Flask/Django想做异步迁移的团队也适合刚入门FastAPI但不想写出玩具项目的同学。2. 工程化起步目录结构、依赖管理与配置分层很多FastAPI新手项目长一个样main.py里堆了两千行路由models和schemas混在一起数据库连接直接写在路由函数里。这种写法跑Demo没问题一旦要加权限、加缓存、加定时任务改一个东西牵一发动全身。我的建议是从第一天就按下面这个结构搭后面每多一个模块都是往里面加文件夹而不是重新组织代码。2.1 一套能用一年不重构的目录结构我目前保持的项目结构长这样app/ ├── main.py # 应用入口创建FastAPI实例、注册路由 ├── core/ │ ├── config.py # 基于pydantic-settings的配置类 │ ├── database.py # 异步engine、sessionmaker、get_db依赖 │ ├── security.py # 密码哈希、JWT签发与校验 │ └── logging.py # 日志配置JSON格式方便采集 ├── models/ # SQLAlchemy ORM模型 │ ├── __init__.py │ └── user.py ├── schemas/ # Pydantic数据模型 │ ├── __init__.py │ └── user.py ├── api/ │ ├── __init__.py │ ├── deps.py # 公共依赖get_db、get_current_user等 │ └── routes/ │ ├── __init__.py │ └── users.py ├── services/ # 业务逻辑层避免路由函数里堆复杂逻辑 │ └── user_service.py ├── tests/ │ ├── conftest.py │ └── test_users.py ├── pyproject.toml # 现代Python项目的依赖与配置声明 ├── .env.example # 环境变量样例方便同事快速起步 └── docker-compose.yml # 本地开发用的PostgreSQL、Redis把路由、模型、校验模型、业务逻辑分开最大的收益是团队协作时不会互相踩文件。路由只做参数接收和HTTP状态码映射业务逻辑放在services层ORM操作封装在services里而不是散落在路由中。这样单元测试可以直接测services层不需要另起一个测试HTTP客户端测试速度快很多。2.2 依赖管理用pyproject.toml替代requirements.txt我建议新项目直接用pyproject.tomluv或poetry管依赖而不是继续写requirements.txt。原因有两层一是pyproject.toml能区分dependencies和dev-dependencies生产装包时不会把pytest、ruff这些开发工具一起装进去镜像体积能小不少二是它能固定依赖树配合uv.lock或poetry.lock团队里新成员拉代码后执行一条命令就能复原一模一样的环境比手抄版本号可靠得多。一个最小可用的pyproject.toml长这样[project] name modern-api version 0.1.0 requires-python 3.11 dependencies [ fastapi0.115,1.0, uvicorn[standard]0.30, sqlalchemy[asyncio]2.0, asyncpg0.29, pydantic2.7, pydantic-settings2.2, redis5.0, fastapi-cache20.2.1, python-jose[cryptography]3.3, passlib[bcrypt]1.7, ] [project.optional-dependencies] dev [ pytest8.0, pytest-asyncio0.23, httpx0.27, ruff0.4, ]这里特别注意uvicorn[standard]而不是裸uvicorn。standard额外带了uvloop和httptoolsuvloop把事件循环的实现换成了libuv吞吐能比纯Python的asyncio事件循环高不少。很多教程没提这个细节但它在压测数据上的差距是实打实的后面第6节我会给出对比数字。2.3 配置分层环境变量是唯一可信来源配置管理这事看起来简单但最容易出问题的是同一个项目本地跑通了测试环境挂了生产环境又不一样。我现在的做法是所有配置都走环境变量代码里通过pydantic-settings读取环境变量没设置时用.env文件兜底.env不入库。# app/core/config.py from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): app_name: str modern-api environment: str dev # dev / test / prod debug: bool False database_url: str postgresqlasyncpg://user:passlocalhost:5432/app redis_url: str redis://localhost:6379/0 cors_origins: list[str] [http://localhost:3000] jwt_secret: str please-change-me jwt_algorithm: str HS256 access_token_expire_minutes: int 60 * 24 model_config SettingsConfigDict( env_file.env, env_file_encodingutf-8, extraignore, ) settings Settings()关键点在extraignore——当环境变量里混着别的配置时不会报错以及cors_origins用list[str]类型pydantic-settings会自动把逗号分隔的字符串解析成列表。配置类在整个应用里以单例settings暴露任何模块from app.core.config import settings即可。不要在路由里直接os.getenv那会让配置的来源散落到代码各处等要审计配置项时根本找不全。3. 异步SQLAlchemy与数据库层性能的胜负手在连接管理FastAPI本身只是框架真正决定你能扛多少并发的是数据库这一层的资源管理方式。我用SQLAlchemy 2.0的异步版本配合asyncpg驱动这一套组合需要重点理解的三个部分是engine的创建方式、session的作用域、连接池参数。3.1 为什么必须用asyncpg而不是psycopg2psycopg2是PostgreSQL的同步驱动它在FastAPI里也可以用但用法会变成用线程池把同步调用包起来本质上还是在靠线程并发。asyncpg是纯异步驱动底层用Cython实现了PostgreSQL的二进制协议解析单从驱动本身看它的性能就比psycopg2快一截。最关键的是只有asyncpg能跟asyncio事件循环直接协作——数据库返回结果时触发回调事件循环把数据送回给等待中的协程全程不需要线程切换。SQLAlchemy配置示例# app/core/database.py from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession from sqlalchemy.orm import declarative_base from app.core.config import settings engine create_async_engine( settings.database_url, pool_size20, # 连接池维持的连接数 max_overflow10, # 高峰期最多额外创建的连接数 pool_recycle3600, # 连接的最大存活时间秒 pool_pre_pingTrue, # 每次从池中取出连接前先ping一下 echoFalse, ) AsyncSessionLocal async_sessionmaker( engine, class_AsyncSession, expire_on_commitFalse, ) Base declarative_base() async def get_db(): async with AsyncSessionLocal() as session: yield session3.2 连接池参数怎么定不是越大越好pool_size和max_overflow是最容易踩坑的两个参数。很多新手一看压测不过就疯狂加大连接池结果数据库先被打挂了。PostgreSQL的每一个连接背后都是一个操作系统进程连接太多反而会因为上下文切换导致整体吞吐下降。我的经验公式是pool_size 应用实例数 × 单实例的worker数 / 数据库能承受的连接数上限然后留出30%余量。举个例子一台8核的机器跑2个Uvicorn worker每个worker内部的事件循环里数据库查询的并发度通常不需要超过10那pool_size设20就足够了。如果你发现连接池满了、请求开始排队优先检查的是SQL有没有N1、是不是有慢查询而不是无脑调大pool_size。pool_recycle这个参数也容易被忽略。数据库服务端通常会主动回收超过一定时间的空闲连接比如PostgreSQL默认的tcp_keepalives_interval如果客户端池里的连接长时间没被使用再取出来时可能已经被服务端断开了客户端拿到一个僵尸连接就会报错。把pool_recycle设为3600秒再配合pool_pre_pingTrue基本能把这类偶发断连问题规避掉。3.3 依赖注入中的Session生命周期FastAPI官方推荐的get_db写法是async with AsyncSessionLocal() as session: yield session。这里的生命周期是每个请求一个独占session请求开始时就创建请求结束后自动关闭并归还连接。不要试图在模块外面共享一个全局session那会导致并发请求复用同一个事务数据隔离性彻底崩溃。在处理多个数据库操作时记得用一个事务包起来SQLAlchemy 2.0的写法是# app/services/user_service.py from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession async def create_user_with_profile(db: AsyncSession, user_data: dict, profile_data: dict): async with db.begin(): user User(**user_data) db.add(user) await db.flush() # 拿到user.id但不提交 profile UserProfile(user_iduser.id, **profile_data) db.add(profile) # async with db.begin() 块结束时会自动commit await db.refresh(user) return userdb.begin()的好处是块内任何一个操作抛异常整个事务自动回滚不会出现半成功半失败的状态。flush()的作用是把SQL发送到数据库但不开新事务这样你能拿到自增主键而最终提交由begin()块出口统一完成。这个写法在压测里的表现也不错因为写操作被合并到了同一个事务中减少了事务提交的次数。4. Pydantic v2与Schemas层序列化细节决定接口响应速度FastAPI最吸引人的特性之一是用Python类型声明API这套声明的核心是Pydantic v2。但很多人只把它当数据类用没注意到几个和性能强相关的细节。这一节我讲三个点response_model的作用、from_attributesTrue的坑、以及大数据量导出时怎么绕过序列化瓶颈。4.1 response_model不只是文档装饰品我见过很多项目路由函数直接return user不声明response_model。这样做的结果有两种返回ORM对象时FastAPI不知道要过滤哪些字段只能把对象的所有属性都序列化出去可能包含password_hash这类敏感字段。返回dict时如果里面带datetime、UUID这些非JSON原生类型FastAPI虽然能兜底转但性能不理想。正确做法是声明response_model# app/schemas/user.py from datetime import datetime from pydantic import BaseModel, ConfigDict, EmailStr class UserOut(BaseModel): id: int username: str email: EmailStr created_at: datetime model_config ConfigDict(from_attributesTrue) # app/api/routes/users.py from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from app.core.database import get_db from app.models.user import User from app.schemas.user import UserOut router APIRouter(prefix/users, tags[users]) router.get(/{user_id}, response_modelUserOut) async def get_user(user_id: int, db: AsyncSession Depends(get_db)): user await db.get(User, user_id) if not user: raise HTTPException(status_code404, detailUser not found) return userresponse_modelUserOut做了三件事字段过滤、类型转换、响应文档生成。FastAPI会先把ORM对象转成UserOut实例再做JSON序列化。脏数据会被Pydantic的校验逻辑拦住而不是等到了前端才暴露。4.2 from_attributes的坑忘记配置会得到一个诡异的报错当Pydantic v2要直接接收ORM对象时必须配置model_config ConfigDict(from_attributesTrue)。如果不配你会看到类似Userobject has no attributeuser_id或者字段全部为空。更隐蔽的坑是嵌套模型。比如UserOut里嵌套了一个ProfileOutclass ProfileOut(BaseModel): bio: str | None None avatar_url: str | None None model_config ConfigDict(from_attributesTrue) class UserOut(BaseModel): id: int username: str email: EmailStr profile: ProfileOut | None None model_config ConfigDict(from_attributesTrue)如果User模型里没有profile这个属性比如关联关系叫user_profile你需要给ORM模型加一个同名的Python属性或者在Pydantic模型上用alias配合validation_alias。顺手一提SQLAlchemy里关系属性通常懒加载异步场景下手动访问未初始化的关系会抛MissingGreenlet错误。解决方式是在路由里用selectinload或joinedload提前加载from sqlalchemy.orm import selectinload stmt select(User).options(selectinload(User.user_profile)).where(User.id user_id) result await db.execute(stmt) user result.scalar_one_or_none()4.3 大列表导出时的序列化性能接口返回几百个对象时Pydantic v2的Rust核心能轻松处理但到了几万条数据导出的场景逐字段序列化成JSON再返回内存和CPU都不划算。我处理这类需求时会绕过Pydantic的标准序列化直接让数据库产出JSONfrom sqlalchemy import text router.get(/export) async def export_users(db: AsyncSession Depends(get_db)): stmt text( SELECT json_agg( json_build_object( id, id, username, username, email, email, created_at, created_at ) ) AS user_list FROM users WHERE created_at :cutoff ) result await db.execute(stmt, {cutoff: 2024-01-01}) return result.scalar_one()json_agg在PostgreSQL内部完成序列化返回的是一整个JSON字符串Python端只是透传内存占用和CPU开销都极小。这类绕过框架把工作下沉到数据库的思路在大数据量接口里效果立竿见影。但要注意json_build_object写起来比ORM费劲且字段调优要靠SQL适合只读报表场景不适合业务写入链路。5. 中间件与安全配置CORS、限流、超时控制的生产级细节开发环境里跑通FastAPI是一回事部署到生产环境就是另一回事了。热搜词里出现频率最高的“fastapi cors”我单独讲同时把中间件顺序和超时配置一并说清楚这些都是在生产环境摸爬滚打后总结出来的调整项。5.1 CORS配置别把allow_origins写成[*]CORS跨源资源共享是浏览器安全模型的一部分它的核心机制是服务器通过响应头告诉浏览器我允许哪些来源的JavaScript读取我的接口。有些教程为了省事直接allow_origins[*]这在带Cookie的场景下是行不通的——浏览器规范禁止Access-Control-Allow-Credentialstrue和Access-Control-Allow-Origin*同时出现。就算你只是开放API给第三方不带Cookie用*也意味着任何网站都能在用户浏览器里请求你的接口被CSRF类攻击的风险会显著上升。我的配置思路是from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_originssettings.cors_origins, # 明确列出前端域名如 [https://admin.example.com] allow_credentialsTrue, # 允许携带Cookie allow_methods[*], # 或者只写 [GET, POST, PUT, DELETE, OPTIONS] allow_headers[*], # 如需限制用 [Authorization, Content-Type] expose_headers[X-Request-Id], # 前端需要读取的自定义响应头 max_age600, # 预检请求的缓存时间 )max_age600是个很实用的参数它告诉浏览器预检请求OPTIONS的结果可以在10分钟内直接复用不用每次请求都先发一个OPTIONS能减少不少网络往返。如果你发现前端老是报跨域错误优先检查列表里是不是漏了端口号——http://localhost:3000和http://localhost:3001是不同来源。5.2 自定义中间件的顺序问题与避坑FastAPI的中间件是后写的先执行也就是洋葱模型。add_middleware的调用顺序决定了请求进入时的执行顺序最后添加的中间件最先处理请求。CORS中间件、GZip中间件、认证中间件的排列顺序会影响错误响应带不带CORS头以及响应体是否被压缩。一个常见的排查场景前端看到跨域报错但后台日志里明明有请求记录。问题往往出在异常处理中间件在CORS中间件之前抛了异常响应在CORS中间件还没执行时就返回了所以响应头里没有Access-Control-Allow-Origin浏览器就报跨域。解决办法是确保CORS中间件是最外层的——也就是在代码里最后添加它。自定义中间件里还有一个高频坑在异步中间件里做了阻塞调用。比如import time from starlette.middleware.base import BaseHTTPMiddleware class BlockingMiddleware(BaseHTTPMiddleware): async def dispatch(self, request, call_next): time.sleep(1) # 严重阻塞事件循环所有请求都会被卡住 response await call_next(request) return responsetime.sleep是同步阻塞它会卡住整个事件循环。压测时你会发现所有请求的耗时同时增加了1秒而且QPS不降反降。这里要么用await asyncio.sleep(1)要么干脆别在中间件里做耗时操作放到后台任务或消息队列里处理。中间件里的每个动作都要习惯性地问一句它是协程吗它会阻塞吗5.3 限流与超时快速失败优于无限等待生产环境最怕的不是请求多而是某个下游接口响应越来越慢把应用服务器的线程和连接全部占满最终拖垮整个服务。限流和超时是两道保险。限流我推荐用slowapi或网关侧限流直接写在应用里虽然方便但分布式多实例部署时每实例各算各的总限额其实是实例数乘以单实例限额容易误判。如果只是单实例内部限流slowapi配合Redis做分布式计数也可以。from slowapi import Limiter from slowapi.util import get_remote_address limiter Limiter(key_funcget_remote_address) app.state.limiter limiter router.get(/search) limiter.limit(100/minute) # 每个IP每分钟最多100次 async def search(request: Request, q: str): return {q: q}超时控制则分两层一层是Uvicorn的--timeout-keep-alive控制HTTP keep-alive连接的空闲时间一层是应用内对Redis、数据库、下游HTTP请求的超时。Redis客户端可以设置socket_timeout和socket_connect_timeouthttpx客户端可以设置timeout参数。原则是所有IO调用都必须有超时超时后快速失败并返回503而不是让用户无限转圈。6. 压测实录从641 QPS到2840 QPS我做了哪四件事空谈性能没有意义我拿一个真实项目片段来讲压测和调优的过程。背景是一个用户查询接口依赖PostgreSQL里的用户表和订单表返回用户基本信息加最近订单数。压测工具用wrk部署在局域网里的一台8核16G的Linux服务器上目标是把单实例QPS从六百多拉到两千五以上。6.1 第一轮压测基线数据与痛点定位初始代码是最普通的同步ORM写法路由函数用def而非async def数据库操作用psycopg2同步执行。wrk压测结果指标数值并发连接数100压测时长30秒总请求数19,235QPS641平均响应时间155msP99响应时间486ms错误率0%看着响应时间还行但QPS只有641而且CPU利用率只有30%左右。这说明瓶颈不在CPU而是在线程切换和同步IO等待上。每个请求占用一个线程等待数据库返回100个并发就要100个线程线程切换开销和GIL竞争把CPU吃掉了大半但有效吞吐上不去。6.2 第二到第四轮优化三个改动叠加第一处改动把路由函数从def改成async def数据库层换成SQLAlchemy异步 asyncpg。这一步让单worker就能同时处理大量等待中的IO不再依赖线程。改动后压测QPS到了2416提升明显但P99响应时间还是有波动于是我开始查慢在哪。第二处改动优化数据库查询去掉N1。原来的查询逻辑是先查用户再循环查每个用户的订单数。压测时数据库端的查询量是接口请求量的数倍连接池很快被打满。改成一次LEFT JOIN聚合查询后数据库查询量降为原来的30%SELECT u.id, u.username, u.email, u.created_at, COUNT(o.id) AS order_count FROM users u LEFT JOIN orders o ON o.user_id u.id WHERE u.id :user_id GROUP BY u.id;第三处改动给热数据加Redis缓存。用户信息和订单数这种读多写少的数据查一次数据库后塞进Redis设5分钟过期再次请求直接走缓存。fastapi-cache2可以无侵入地给路由加缓存from fastapi_cache.decorator import cache router.get(/users/{user_id}/summary, response_modelUserSummaryOut) cache(expire300) async def get_user_summary(user_id: int, db: AsyncSession Depends(get_db)): # 查询逻辑 ...加缓存后接口在大量重复请求下不再打到数据库QPS进一步上涨P99显著下降。三轮优化后的最终数据指标优化前优化后QPS6412840平均响应时间155ms36msP99响应时间486ms108ms数据库查询次数/请求~4~0.3缓存命中时趋近于0CPU利用率30%62%6.3 这个压测过程给我的三个认知第一性能优化的顺序必须是先架构后参数。异步化、查询优化、缓存这几个结构性改动带来的收益是几千QPS级别的而调pool_size、换JSON库这类参数级优化通常只是百分之几十的提升。不要在架构没理顺时扣参数细节。第二压测必须带数据库和缓存一起压。只对接口层压测接口里不访问任何IO测出来的QPS是纯CPU和框架的开销跟你真实上线后的表现没有任何关系。我自己见过太多压测两万QPS上线后两百都费劲的案例基本都是压测链路不完整。第三监控永远优先于压测。先接入指标采集Prometheus Grafana把响应时间、数据库慢查询、连接池使用率、GC次数这些指标捞出来再压测你才知道瓶颈在哪。我第一轮调优时没接监控全靠猜效率极低。7. 深夜排障记录FastAPI项目里最容易骗人的四个坑最后这部分我按真实排障的时间顺序记录几个我在FastAPI项目里踩过、也帮同事排查过的坑。每一个都在网上被反复问过但真相往往藏在文档的角落。7.1 MissingGreenlet异步ORM里不可见的懒加载陷阱这是SQLAlchemy异步版本最常见的报错没有之一。报错信息通常是sqlalchemy.exc.MissingGreenlet: greenlet_spawn has not been called; cant call await_only() here。新手看到这个报错完全懵以为是自己代码写错了。原因很简单你定义了一个ORM关系属性比如User.addresses然后在异步代码里访问了它。由于没有提前加载SQLAlchemy尝试懒加载——但懒加载是同步IO在async环境里没法执行于是抛MissingGreenlet。解决方式我在4.2节提到过用selectinload或joinedload显式加载或者查询时把需要的列用with_entities取出来。这个坑的隐蔽之处在于数据量小时你可能永远不触发它一旦某个用户关联数据多了接口就开始随机报错。7.2 Uvicorn热重载在生产环境的连锁反应--reload参数在开发时很爽代码一保存服务就自动重启。但有同事把它带到了生产环境后果是每次代码更新都会触发worker重启重启瞬间所有进行中的请求被强行中断前端报502/503。如果同时还配了多worker还会出现部分worker是新代码部分worker是旧代码的混合状态排查起来非常痛苦。生产环境的正确姿势是--reload完全不用代码更新靠容器编排的滚动重启配合健康检查接口/healthz等待服务就绪后再切流量。Uvicorn的--workers 2在单机场景下可以开但要注意数据库连接池的总量是2倍别把连接池撑爆。7.3 大小写转换当数据库是snake_case前端要camelCase国内很多团队的后端习惯是created_at这种snake_case但前端JavaScript的约定是createdAt。如果接口直接返回created_at前端要么每次手动转换要么维护一套映射。我在FastAPI里用了Pydantic的alias_generator来解决from pydantic import BaseModel, ConfigDict from pydantic.alias_generators import to_camel class ApiModel(BaseModel): model_config ConfigDict( alias_generatorto_camel, populate_by_nameTrue, from_attributesTrue, ) class UserOut(ApiModel): id: int username: str created_at: datetime这样UserOut在JSON序列化时字段自动变成createdAt前端拿到的就是符合他们约定的格式。populate_by_nameTrue保证后端内部用created_at也能赋值。这个统一基类放好后新写一个schema只要继承ApiModel就行不用每个类都配一遍config。7.4 Python版本与依赖版本的上限锁定FastAPI生态更新很快我遇到过因为依赖版本没有锁好导致某次pip install把pydantic从v1升到v2结果整个项目爆出一堆DeprecationWarning甚至直接跑不起来。现代Python项目一定要用锁文件uv.lock或poetry.lock并且CI里加上每天检测依赖更新的自动化流程由专门的PR来处理升级而不是某天顺手pip install --upgrade一下把依赖全升了。另外Python版本也建议锁到3.11以上。3.11在异常处理、类型检查、asyncio上的性能改进非常明显async/await场景下大概能比3.10快10%到20%。到了3.12、3.13性能还在继续提升。如果你还在用Python 3.8/3.9跑FastAPI升级Python版本可能是性价比最高的性能优化。我的最终建议性能和可维护性要一起设计做了这么多轮优化和排障我最大的感受是高性能的API不是靠堆配置堆出来的而是靠一层层的结构性优化叠出来的。异步化的选型、连接池的管控、查询的合并、缓存的引入、CORS和超时这些生产级细节每一个单独看都不复杂但把它们组合在一起才是一个能在真实流量面前站得住的现代API。如果你正准备用FastAPI起一个新项目我的建议是目录结构和配置分层在一开始就按生产标准来Uvicorn记得用standard版本数据库层直接用异步SQLAlchemy和asyncpgPydantic模型统一设置from_attributesTrue和别名规则中间件里不做任何阻塞操作。这套组合拳打下来虽然刚开始写代码时比把所有东西塞进main.py多花半天时间但后面每次加功能、每次排障都会省回远超这半天的精力。最后分享一个每次压测前我都会检查小习惯把日志级别调到INFO以下因为生产环境密密麻麻的访问日志本身就会成为性能瓶颈特别是日志框架的同步IO写磁盘在高峰期能吃掉不少耗时。日志该采样就采样该用异步handler就用异步handler这是最容易被遗忘的隐形性能杀手。