什么是FastAPI?
FastAPI 是一个现代、高性能且易于使用的 Python Web 框架,专门用于构建 RESTful API 服务。它基于 Python 3.6+ 的类型提示,并依赖于 Starlette(轻量 ASGI 框架)和 Pydantic(数据验证与序列化库)构建而成。
核心特点
- 高性能:FastAPI 使用了 uvicorn,一个基于 uvloop 的异步服务器,因此具有非常高的性能。与 Node.js 和 Go 相媲美,是目前最快的 Python Web 框架之一。
- 自动文档生成:FastAPI 使用了类型提示,因此可以轻松地定义 API 的输入和输出参数,并自动生成 Swagger UI 和 ReDoc 交互式 API 文档。
- 类型提示:利用 Python 类型注解进行数据验证、序列化与 IDE 智能补全。
- 异步支持:原生支持 async/await,适合高并发和 IO 密集型任务。
- 数据验证:基于 Pydantic,自动校验请求和响应数据格式,减少运行时错误。
常见使用场景
- 构建 RESTful API 后端服务
- 微服务架构 中的轻量级服务
- 实时通信(支持 WebSocket)
- 数据接口服务(如 GraphQL、OpenAPI 等)
FastAPI和主流的Python Web框架比较
| 维度 | Django | Flask | FastAPI |
|---|---|---|---|
| 定位 | 全栈“全家桶” | 轻量级微框架 | 高性能 API 框架 |
| 架构 | MTV(类 MVC) | WSGI 微核心 | ASGI 异步原生 |
| 性能(简单接口) | ~15 ms | ~8 ms | ~3 ms |
| 并发能力 | ~1 000 RPS | ~2 500 RPS | ~6 000 RPS |
| 异步支持 | 需 Channels 扩展 | 需外部库 | 原生 async/await |
| 自动 API 文档 | 需 drf-yasg 等 | 需 flask-restx 等 | Swagger UI & ReDoc 开箱即用 |
| 数据验证 | Form + ORM | 手动或扩展 | Pydantic 自动校验 |
| 内置 ORM / Admin | ✅ 完整 ORM + Admin | ❌ 需 SQLAlchemy 等 | ❌ 可自由选型 |
| 学习曲线 | 陡峭 | 平缓 | 中等 |
| 适用场景 | CMS、电商、后台管理 | 微服务、小型站点 | 高并发 API、ML 推理、实时推送 |
| 通过这个比较可以看出来,FastAPI 适合高并发、高性能的 API 服务,而 Django 和 Flask 适合开发 Web 应用。尤其是在机器学习推理相关的应用中,FastAPI的高并发,低延迟更具优势,所以还是值得关注下FastAPI这个框架。 |
代码示例
入门必备的Hello World代码:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello, FastAPI!"}
运行这个代码,访问 http://127.0.0.1:8000/,可以看到返回 {"message": "Hello, FastAPI!"}。
pip install fastapi uvicorn
uvicorn main:app --reload
到此为止,恭喜你,可以说已经完成了FastAPI的入门。就是这么简单!
会写Hello World真的够了嘛?
只是用来显摆下会用FastAPI,这就差不多了。但是如果想用FastAPI做项目,还有很多待解决的问题:
- 如何添加数据库支持?
- 如何处理用户认证?
- 如何处理文件上传?
- 如何处理错误?
- 如何处理并发? 等等...
先来看下,一般FastAPI的项目结构是怎么样的?
最简单的,或者说最小MVP(单文件)
myapi/
├── main.py # 所有路由、模型、启动放一起
└── requirements.txt
main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root():
return {"msg": "Hello FastAPI!"}
启动
uvicorn main:app --reload
发现没有,这个就是我们的DEMO了。所以说,FastAPI入门很简单吧。这个模式,适用这类项目 demo、脚本、一次性接口。。
最常用的项目结构,标准中型结构
myapi/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI 实例 & 全局中间件
│ ├── api/ # 按业务划分子路由
│ │ ├── __init__.py
│ │ ├── v1/
│ │ │ ├── __init__.py
│ │ │ ├── users.py # 用户相关路由
│ │ │ └── items.py
│ ├── core/ # 配置、安全、常量
│ │ ├── config.py
│ │ └── security.py
│ ├── models/ # Pydantic 模型
│ │ ├── __init__.py
│ │ └── user.py
│ ├── crud/ # 数据库操作
│ │ └── user.py
│ └── db/
│ ├── __init__.py
│ └── session.py # 创建 engine / SessionLocal
├── tests/
│ └── test_user.py
├── .env # 环境变量
├── requirements.txt
└── Dockerfile
启动
uvicorn app.main:app --reload
特点
- 分层清晰(路由 / 业务 / 数据 / 配置)
- 可扩多版本(v1、v2 并存)
- 单测、Docker、CI 模板齐全
生产级微服务大仓(多服务复用)
backend/
├── gateway/ # BFF / 网关
├── services/
│ ├── user-service/
│ ├── order-service/
│ └── notification-service/
│ ├── app/
│ │ ├── main.py
│ │ ├── api/
│ │ ├── models/
│ │ └── ...
│ ├── tests/
│ ├── Dockerfile
│ └── requirements.txt
├── shared/ # 公共库(日志、模型、工具)
│ ├── logger.py
│ └── models/
├── k8s/ # Helm Chart / manifests
├── docker-compose.yml
└── skaffold.yaml
- 每个服务独立 FastAPI 实例
- 共享代码通过 内部 wheel / git submodule 管理
- 部署:Docker → K8s / Skaffold / Helm
接下来,就用最常用的中型结构,来写一个完整的项目。
MarkDown 转 PDF
为什么要做这个功能?Markdown 写得爽,PDF 拿得出手,一键让技术文档秒变正式报告。或者是要发小红书,最终可以是PDF再转成一页一页的图片。
项目需求
# 项目需求文档(PRD)
**项目名称**:Markdown 转 PDF Web工具
**版本**:v1.0
**作者**:Demo PM
**日期**:2025-08-05
---
## 1. 项目背景
技术团队日常用 Markdown 编写方案、周报、SOP,但对外交付、审计、客户汇报必须提供 **排版统一的 PDF**。
现有做法:
- 人工复制到 Word → 调格式 → 导出 PDF,耗时 15-30 min/次,易出错。
**机会点**:一条自动化流水线 **“Markdown → PDF”**,节省人力,统一品牌视觉。
---
## 2. 目标与范围
| 目标 | 指标 |
|---|---|
| 让任何同事把 .md 文件拖进浏览器即可下载 PDF | 单文件 ≤ 5 s 完成 |
| 生成的 PDF 符合公司视觉规范 | 字体、页眉、页脚 100% 命中模板 |
**本期做**
- 单文件上传、UTF-8 文件名、PDF 下载、24h 有效期
**本期不做**
- 多文件批量、在线预览、付费套餐
---
## 3. 用户故事
| 角色 | 故事 | 验收标准(GWT) |
|---|---|---|
| 技术工程师 | 写完设计文档后,能在 10 秒内拿到 PDF | Given 已写完 `design.md`<br>When 上传文件<br>Then 5 秒内浏览器自动下载 `design.pdf` |
| 项目经理 | 给客户的周报无需再手动排版 | Given 周报 repo 更新<br>When CI 触发<br>Then 邮件附带 PDF 附件 |
---
## 4. 功能需求
| 编号 | 描述 | 验收点 |
|---|---|---|
| FR-1 | 文件上传 | 支持拖拽/点击,大小 ≤ 100 MB |
| FR-2 | 文件名解码 | `%E4%BD%A0.md` → `你.md` |
| FR-3 | 异步转 PDF | FastAPI + ReportLab,不阻塞其他请求 |
| FR-4 | 下载链接 | 7 位随机码 + 24 h 有效期 |
---
## 5. 非功能需求
- **性能**:并发 ≥ 500 RPS;P99 ≤ 800 ms(100 KB 文件)
- **可靠性**:失败率 ≤ 0.1%,自动重试 3 次
- **安全**:文件类型白名单 `.md/.markdown`,扫描病毒
- **可观测**:Prometheus 指标 `md2pdf_requests_total`
---
## 6. 原型 & 交互

1. 拖放区 → 2. 进度条 → 3. 下载按钮
---
## 7. 技术方案
| 层级 | 选型 | 理由 |
|---|---|---|
| 框架 | FastAPI + uvicorn | 原生异步,Swagger 自动生成 |
| 转换库 | ReportLab + markdown2 | Python 内一站式,无需 Pandoc |
| 存储 | 临时本地卷 + S3 备份 | 简化部署,后续可切 MinIO |
| 部署 | Docker + K8s| 按需水平扩容 |
---
## 8. 里程碑
| 阶段 | 日期 | 交付物 |
|---|---|---|
| 需求冻结 | 2025-08-10 | PRD v1.0 |
| 开发完成 | 2025-08-20 | 代码 + 单元测试覆盖率 ≥ 90% |
| 灰度上线 | 2025-08-25 | 20% 流量,监控 OK |
| 全量上线 | 2025-08-30 | 100% 流量,周报复盘 |
---
## 9. 风险 & 对策
| 风险 | 概率 | 对策 |
|---|---|---|
| 大文件 OOM | 中 | 限制 100 MB + 流式读取 |
| 字体版权纠纷 | 低 | 使用开源思源黑体 |
| 并发洪峰 | 低 | K8s HPA + 队列削峰 |
---
## 10. 附录
- 术语表:SLA、RPS、P99
- 参考链接:
- FastAPI 官方文档 https://fastapi.tiangolo.com
- ReportLab 用户手册
核心代码注意点
1. 文件的读写需要异步的方式
FastAPI中的请求采用异步时间的方式,如果请求中涉及到文件的读写,则需要使用异步的方式来处理,否则可能会导致请求阻塞。
异步读写文件,用aiofiles模块
async with aiofiles.open(source_file_path, "wb") as source_output_file:
while chunk := await file.read(1024 * 1024):
await source_output_file.write(chunk)
想要测试异步时间请求中,同步带来的阻塞影响,可以使用time模块来测试 例如:
import time
@router.get("/test_sync_time/")
async def test_sync_time():
time.sleep(2) # 模拟同步请求耗时
return {"message": "Hello World"}
模拟同时5个并发请求
async def a_sync_requests():
tasks = []
for i in range(5):
tasks.append(asyncio.create_task(a_sync_request()))
await asyncio.gather(*tasks)
async def a_sync_request():
async with aiohttp.ClientSession() as session:
async with session.get(
'http://localhost:8000/test_sync_time/',
) as response:
result_json = await response.json()
print(result_json)
@pytest.mark.asyncio
async def test_a_sync_sync_time():
s = time.monotonic()
await a_sync_requests()
print('执行时间:', time.monotonic() - s)
执行结果:
{"message": "Hello World"}
{"message": "Hello World"}
{"message": "Hello World"}
{"message": "Hello World"}
{"message": "Hello World"}
执行时间: 10.000000000000582
使用异步time sleep方法
@router.get("/test_async_time/")
async def test_sync_time():
await asyncio.sleep(2) # 测试代码 # 模拟异步请求耗时
return {"message": "Hello World"}
模拟同时5个并发请求
async def a_async_requests():
tasks = []
for i in range(5):
tasks.append(asyncio.create_task(a_async_request()))
await asyncio.gather(*tasks)
async def a_async_request():
async with aiohttp.ClientSession() as session:
async with session.get(
'http://localhost:8000/test_sync_time/',
) as response:
result_json = await response.json()
print(result_json)
@pytest.mark.asyncio
async def test_a_sync_sync_time():
s = time.monotonic()
await a_async_requests()
print('执行时间:', time.monotonic() - s)
执行结果:
{"message": "Hello World"}
{"message": "Hello World"}
{"message": "Hello World"}
{"message": "Hello World"}
{"message": "Hello World"}
执行时间: 2.010000000000582
通过以上例子可以非常直观的看到,如果在异步事件中,使用了同步代码,则会阻塞事件线程,异步代码变成了同步代码。
2. asyncio.to_thread
asyncio.to_thread 是 Python 3.9 引入的一个函数,用于在异步代码中执行同步阻塞操作,而不会阻塞整个事件循环。
掌握 asyncio.to_thread,你就能在保持代码简洁的同时,充分利用 Python 异步编程的性能优势。
我们的案例里面,reportlab SimpleDocTemplate 的 build 方法并没有提供异步方法,或者异步的库。因此需要用asyncio.to_thread将该方法放到线程中执行,以避免阻塞。
示例
asyncio.to_thread(func, /, *args, **kwargs)
- func:要执行的同步函数。
- *args 和 **kwargs:传递给 func 的参数。 示例 1:执行阻塞的 I/O 操作
import asyncio
import time
def blocking_io():
print("开始读取文件...")
time.sleep(5) # 模拟一个耗时的 I/O 操作
print("文件读取完成")
return "文件内容"
async def main():
print("开始异步任务")
# 使用 to_thread() 在单独的线程中执行阻塞的 I/O 操作
result = await asyncio.to_thread(blocking_io)
print(f"读取到的文件内容: {result}")
print("异步任务完成")
asyncio.run(main())
示例 2:执行阻塞的 CPU 密集型操作
import asyncio
import math
def cpu_bound_task(n):
print(f"开始计算 {n} 的阶乘...")
result = math.factorial(n)
print(f"{n} 的阶乘计算完成")
return result
async def main():
print("开始异步任务")
# 使用 to_thread() 在单独的线程中执行阻塞的 CPU 密集型操作
result = await asyncio.to_thread(cpu_bound_task, 100000)
print(f"计算结果: {result}")
print("异步任务完成")
asyncio.run(main())
注意事项
- 适用场景判断:只有在处理 I/O 密集型或计算密集型的同步任务时才需要 to_thread。
- 线程池大小:默认使用标准库 concurrent.futures.ThreadPoolExecutor,池大小通常为 min(32, os.cpu_count() + 4)。
- 避免过度使用:轻量级操作不需要 to_thread,直接调用即可。
- 异步函数不需要:如果函数本身就是 async def 定义的,直接用 await 调用即可,不需要 to_thread。
- 长时间 CPU 密集型任务:由于 GIL 的限制,对于长时间的 CPU 密集型任务,建议使用多进程(multiprocessing)。
- 超高并发场景:线程太多会有调度开销,对于超高并发场景,建议使用纯异步 I/O。
好了,我们用一个项目大概介绍了FastAPI的用法。入门简单,想用用好FastAPI以及在项目中实践,还是有很多需要学习的内容。