给 FastAPI 后台模板补了一轮生产化能力

Jiayu Xu Lv2

FastAPI Template 是一个企业后台 API 模板,内置 JWT 登录、RBAC、用户/角色/菜单、文件、审计日志、Docker、CI、Redis、任务队列和可观测性。

如果你正准备起一个 FastAPI 管理后台,或者不想每个项目都重复搭一遍权限、审计、缓存和 worker,可以先看一眼这个仓库。觉得省事的话,顺手点个 Star 就行。

这次改动从 Redis 开始。

测试跑完以后连接没关,pytest 的 event loop 一换,缓存 client 就容易留下脏状态。它看起来只是一个测试清理问题,但顺着往下看,会发现后台模板里不少地方都属于“平时不显眼,真跑起来很烦”:接口慢了不好定位,导出任务占住 HTTP 请求,权限改了以后旧缓存还在,Redis 抖一下业务代码先报错。

模板原来已经能跑用户、角色、文件、审计日志这些后台接口。这轮补的是这些运行期的小窟窿。

FastAPI Template 从 demo 走向生产化的系统配图

图先放这里,后面基本就是沿着这三块展开。

health 那个接口

我见过不少健康检查只返回一句:

1
{"status": "healthy"}

只返回 healthy 时,排查空间很小。应用进程活着,不代表数据库能连;接口能返回,不代表 Redis 正常;用户说“刚才保存失败了”,后端也不能只靠日志里搜时间点。

健康检查和请求指标被拆得更细。

可观测性能力示意图

这张图说的是:别只问服务活没活,也要知道依赖和请求链路卡在哪里。

/api/v1/base/health 会探测数据库和 Redis:

1
2
3
4
5
{
"status": "healthy",
"database": "connected",
"redis": "connected"
}

Redis 或数据库异常时,状态会变成 degraded,不用再把“进程存活”和“依赖可用”混在一起看。

Prometheus 这边新增了指标中间件和 /api/v1/base/metrics。请求指标尽量按 FastAPI 的路由模板打点,别直接拿原始 URL 当 label。/api/v1/users/1/api/v1/users/2 应该算同一个接口,不然路径参数一多,Prometheus 的时序数量会膨胀,也就是 cardinality 爆炸。

指标里会记录这些基础数据:

  • 请求总数
  • 请求耗时直方图
  • 正在处理的请求数
  • Redis 和数据库可用状态

X-Request-ID 做的是另一件小事:把一次请求串起来。上游传了请求 ID 就继续透传,没有的话服务自己生成一个,并写回响应头。用户反馈问题时,前端、网关、后端日志可以对同一个 ID,不用靠时间点盲猜。

Sentry 做成可选项。配置 SENTRY_DSN 就启用,不配置就跳过。项目里只保留接入点,不绑定某一套错误平台。

排障时可以先看依赖状态和请求 ID,再看业务日志。

worker 终于有地方放了

后台里最容易被临时塞进接口的,通常是发邮件、导出报表、同步第三方、清理历史数据这些事。早期这么写很快,后来就会变成接口超时、失败没法重试、用户一直看转圈。

这里用 arq 和 Redis 做队列,让 Web 进程和 worker 分开。

arq 异步任务队列示意图

图里最重要的是 job id 这条回路:接口返回它,前端拿它查状态。

Web 进程只做入队,尽快把 job id 返回出去:

1
await enqueue_task("send_email_task", to=email, subject=subject, body=body)

worker 进程独立执行任务:

1
PYTHONPATH=src uv run arq tasks.worker.WorkerSettings

我放了两个例子,后续业务可以照这个形状接:

  • send_email_task:邮件任务占位,方便后面接 SMTP 或第三方邮件服务
  • cleanup_audit_logs_task:每天凌晨清理 90 天前的审计日志

/api/v1/tasks/{job_id} 用来查任务状态。导出、同步、批处理这类任务可以返回 job id,前端轮询状态,HTTP 连接不需要一直被占着。

先把入队、执行、状态查询这条路径打通。arq + Redis 跟 FastAPI 的 async 生态比较贴。

遇到第一个后台任务时,入队和查询状态的写法已经有了。

cache.py 这次动得最多

Redis 接上以后,麻烦通常出在异常和失效上。

最朴素的写法大概是:

1
2
3
4
data = await redis.get(key)
if not data:
data = await query_database()
await redis.set(key, data)

这段能用,但跑久了会碰到这些事:

  • Redis 连接断了,业务要不要跟着失败?
  • 大量 key 同时过期,会不会一起打到数据库?
  • 查不到的数据要不要缓存?
  • 权限变了,旧权限缓存怎么失效?
  • pytest 里 event loop 换了,Redis client 会不会复用出问题?

这次把对应处理补进去了。

缓存与 RBAC 权限性能示意图

这里有两条线:缓存命中怎么快,权限变更后旧缓存怎么退场。

Redis 不可用时走降级逻辑。缓存连接失败不会阻塞应用启动,读写失败会丢弃旧连接,后续再按需重连。否则缓存层比业务逻辑更早把服务拖垮。

TTL 也加了抖动。比如默认 300 秒,各个 key 不再精确同一秒过期,而是在基础 TTL 上加一点随机偏移,减少一批缓存同时失效后打到数据库。

空值缓存也补上了。某些查询确认不存在时,会写一个短 TTL 的空值哨兵,避免同一个不存在的数据被反复打到数据库。后台列表、详情、权限查询都可能遇到这种穿透。

权限缓存单独处理。普通用户访问接口时,需要根据角色匹配 API 权限。旧逻辑容易重复查角色、重复查 API、重复编译路径正则。用户的 API 权限列表会缓存 5 分钟,路径匹配规则用 functools.lru_cache 缓存编译结果。

角色权限、API 权限变更后,会清理用户权限缓存。权限数据读到旧结果会直接影响放行判断。

改完之后,RBAC 校验少了一些重复查询和重复正则编译,权限变更后也会触发缓存清理。

  • 标题: 给 FastAPI 后台模板补了一轮生产化能力
  • 作者: Jiayu Xu
  • 创建于 : 2026-05-09 14:41:07
  • 更新于 : 2026-05-09 14:42:38
  • 链接: https://www.rasior.com/2026/05/09/20260509_fastapi-template-production-updates/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
目录
给 FastAPI 后台模板补了一轮生产化能力