Python asyncio 实践:真实测量数据
Python 是在执行模型方面与 PHP 最相似的语言: 解释型、单线程(GIL)、同步框架占主导地位。 从同步 Python(Flask、Django + Gunicorn)到异步 (FastAPI、aiohttp、Starlette + Uvicorn)的过渡, 是从 PHP-FPM 到基于协程运行时过渡的精确类比。
以下是生产案例、独立基准测试和测量数据的汇总。
1. 生产案例:Duolingo — 迁移到异步 Python(吞吐量 +40%)
Duolingo 是最大的 语言学习平台(5 亿以上用户)。 后端使用 Python 编写。
2025 年,团队开始系统地将服务从同步 Python 迁移到异步。
| 指标 | 结果 |
|---|---|
| 每实例吞吐量 | +40% |
| AWS EC2 成本节省 | 每个已迁移服务 约 30% |
作者指出,在构建好异步基础设施后,迁移 各个服务变得”相当简单”。
来源: How We Started Our Async Python Migration (Duolingo Blog, 2025)
2. 生产案例:Super.com — 成本降低 90%
Super.com(前身为 Snaptravel)是一个酒店搜索 和折扣服务。其搜索引擎处理 1,000+ req/s, 每天摄入 1 TB+ 的数据,每日处理 100 万美元以上的销售额。
关键工作负载特征: 每个请求向第三方 API 发起 40 次以上网络调用。 这是纯粹的 I/O 密集型场景 — 协程的理想候选者。
团队从 Flask(同步,AWS Lambda)迁移到 Quart(ASGI,EC2)。
| 指标 | Flask (Lambda) | Quart (ASGI) | 变化 |
|---|---|---|---|
| 基础设施成本 | ~$1,000/天 | ~$50/天 | -90% |
| 吞吐量 | ~150 req/s | 300+ req/s | 2 倍 |
| 高峰时段错误 | 基准值 | -95% | -95% |
| 延迟 | 基准值 | -50% | 快 2 倍 |
每天节省 $950 × 365 = 每年约 $350,000,仅一项服务。
来源: How we optimized service performance using Quart ASGI and reduced costs by 90% (Super.com, Medium)
3. 生产案例:Instagram — 5 亿 DAU 规模下的 asyncio
Instagram 在 Django 后端上服务 5 亿以上日活用户。
Jimmy Lai(Instagram 工程师)在 PyCon Taiwan 2018 的演讲中 描述了迁移到 asyncio 的过程:
- 将
requests替换为aiohttp用于 HTTP 调用 - 将内部 RPC 迁移到
asyncio - 实现了 API 性能改善和 CPU 空闲时间减少
挑战: 在 Instagram 的规模下 asyncio 的 CPU 开销较高, 需要通过静态代码分析自动检测阻塞调用。
来源: The journey of asyncio adoption in Instagram (PyCon Taiwan 2018)
4. 生产案例:Feature Store — 从线程迁移到 asyncio(延迟 -40%)
Feature Store 服务从 Python 多线程迁移到 asyncio。
| 指标 | 线程 | Asyncio | 变化 |
|---|---|---|---|
| 延迟 | 基准值 | -40% | -40% |
| RAM 消耗 | 18 GB(数百个线程) | 显著减少 | 大幅降低 |
迁移分三个阶段进行,使用 50/50 的生产流量 分流进行验证。
来源: How We Migrated from Python Multithreading to Asyncio (Medium)
5. 生产案例:Talk Python — Flask 到 Quart(延迟 -81%)
Talk Python 是最大的 Python 播客 和学习平台之一。作者(Michael Kennedy)将网站 从 Flask(同步)重写为 Quart(异步 Flask)。
| 指标 | Flask | Quart | 变化 |
|---|---|---|---|
| 响应时间(示例) | 42 ms | 8 ms | -81% |
| 迁移后 Bug | — | 2 | 极少 |
作者指出:在负载测试中,最大 req/s 差异不大,因为 MongoDB 查询花费不到 1 ms。 收益出现在并发请求处理时 — 当多个客户端同时访问服务器时。
来源: Talk Python rewritten in Quart (async Flask)
6. Microsoft Azure Functions — uvloop 作为标准
Microsoft 将 uvloop — 一个基于 libuv 的快速事件循环 — 作为 Python 3.13+ 的 Azure Functions 的默认配置。
| 测试 | 标准 asyncio | uvloop | 提升 |
|---|---|---|---|
| 10K 请求,50 VU(本地) | 515 req/s | 565 req/s | +10% |
| 5 分钟,100 VU(Azure) | 1,898 req/s | 1,961 req/s | +3% |
| 500 VU(本地) | 720 req/s | 772 req/s | +7% |
标准事件循环在 500 VU 时出现约 2% 的请求丢失。 uvloop — 零错误。
来源: Faster Python on Azure Functions with uvloop (Microsoft, 2025)
7. 基准测试:IO 密集型任务 — asyncio 快 130 倍
下载 10,000 个 URL 任务的并发模型直接比较:
| 模型 | 时间 | 吞吐量 | 错误 |
|---|---|---|---|
| 同步 | ~1,800 s | ~11 KB/s | — |
| 线程 (100) | ~85 s | ~238 KB/s | 低 |
| Asyncio | 14 s | 1,435 KB/s | 0.06% |
Asyncio:比同步代码快 130 倍,比线程快 6 倍。
对于 CPU 密集型任务,asyncio 没有优势 (时间相同,内存消耗 +44%)。
来源: Python Concurrency Model Comparison (Medium, 2025)
8. 基准测试:uvloop — 比 Go 和 Node.js 更快
uvloop 是标准 asyncio 事件循环的直接替代品,使用 Cython 编写,基于 libuv(与 Node.js 使用的相同库)。
TCP echo 服务器:
| 实现 | 1 KiB (req/s) | 100 KiB 吞吐量 |
|---|---|---|
| uvloop | 105,459 | 2.3 GiB/s |
| Go | 103,264 | — |
| 标准 asyncio | 41,420 | — |
| Node.js | 44,055 | — |
HTTP 服务器(300 并发):
| 实现 | 1 KiB (req/s) |
|---|---|
| uvloop + httptools | 37,866 |
| Node.js | 更低 |
uvloop:比标准 asyncio 快 2.5 倍,比 Node.js 快 2 倍, 与 Go 持平。
来源: uvloop: Blazing fast Python networking (MagicStack)
9. 基准测试:aiohttp vs requests — 并发请求快 10 倍
| 库 | req/s(并发) | 类型 |
|---|---|---|
| aiohttp | 241+ | 异步 |
| HTTPX (async) | ~160 | 异步 |
| Requests | ~24 | 同步 |
aiohttp:在并发 HTTP 请求中比 Requests 快 10 倍。
来源: HTTPX vs Requests vs AIOHTTP (Oxylabs)
10. 反面论据:Cal Paterson — “异步 Python 并不更快”
呈现反面论据同样重要。Cal Paterson 使用真实数据库 (PostgreSQL,随机行选择 + JSON)进行了彻底的基准测试:
| 框架 | 类型 | req/s | P99 延迟 |
|---|---|---|---|
| Gunicorn + Meinheld/Bottle | 同步 | 5,780 | 32 ms |
| Gunicorn + Meinheld/Falcon | 同步 | 5,589 | 31 ms |
| Uvicorn + Starlette | 异步 | 4,952 | 75 ms |
| Sanic | 异步 | 4,687 | 85 ms |
| AIOHTTP | 异步 | 4,501 | 76 ms |
结果: 使用 C 服务器的同步框架表现出更高的吞吐量 和 2-3 倍更好的尾部延迟(P99)。
为什么异步输了?
原因:
- 每个 HTTP 请求只有一条 SQL 查询 — I/O 太少, 协程并发无法发挥作用。
- 在请求之间进行 CPU 工作的协作式多任务 造成了”不公平”的 CPU 时间分配 — 长计算会为所有人阻塞事件循环。
- asyncio 开销(Python 中的标准事件循环) 在 I/O 很少时与非阻塞 I/O 的收益相当。
异步实际在何时有帮助
Paterson 的基准测试测试的是最简单的场景(1 条 SQL 查询)。 正如上面的生产案例所展示的,异步在以下情况下提供巨大收益:
- 有很多 数据库/外部 API 查询(Super.com:每请求 40+ 次调用)
- 高并发(数千个同时连接)
- I/O 远超 CPU(Duolingo、Appwrite)
这与理论一致: 阻塞系数(T_io/T_cpu)越高,协程的收益越大。 当只有 1 条 SQL 查询 × 2 ms 时,系数太低。
来源: Async Python is not faster (Cal Paterson)
11. TechEmpower:Python 框架
TechEmpower Round 22 的近似结果:
| 框架 | 类型 | req/s (JSON) |
|---|---|---|
| Uvicorn (raw) | Async ASGI | Python 中最高 |
| Starlette | Async ASGI | ~20,000–25,000 |
| FastAPI | Async ASGI | ~15,000–22,000 |
| Flask (Gunicorn) | Sync WSGI | ~4,000–6,000 |
| Django (Gunicorn) | Sync WSGI | ~2,000–4,000 |
异步框架:在 JSON 测试中比同步框架快 3-5 倍。
来源: TechEmpower Framework Benchmarks
总结:Python 数据展示了什么
| 案例 | 同步 → 异步 | 条件 |
|---|---|---|
| Duolingo(生产) | 吞吐量 +40%,成本 -30% | 微服务,I/O |
| Super.com(生产) | 吞吐量 2 倍,成本 -90% | 每请求 40+ API 调用 |
| Feature Store(生产) | 延迟 -40% | 从线程迁移到 asyncio |
| Talk Python(生产) | 延迟 -81% | Flask → Quart |
| IO 密集型(10K URLs) | 快 130 倍 | 纯 I/O,大规模并发 |
| aiohttp vs requests | 快 10 倍 | 并发 HTTP 请求 |
| uvloop vs 标准 | 快 2.5 倍 | TCP echo,HTTP |
| TechEmpower JSON | 3-5 倍 | FastAPI/Starlette vs Flask/Django |
| 简单 CRUD(1 条 SQL) | 同步更快 | Cal Paterson:异步 P99 差 2-3 倍 |
| CPU 密集型 | 无差异 | 内存 +44%,收益 0% |
关键结论
异步 Python 在高阻塞系数下提供最大收益: 当 I/O 时间远超 CPU 时间时。 有 40+ 网络调用时(Super.com)— 节省 90% 成本。 只有 1 条 SQL 查询时(Cal Paterson)— 异步更慢。
这验证了 IO 密集型任务效率中的公式: 收益 ≈ 1 + T_io/T_cpu。当 T_io » T_cpu 时 — 数十到数百倍。 当 T_io ≈ T_cpu 时 — 极小或为零。
与 PHP 和 True Async 的关联
Python 和 PHP 处于相似的境况:
| 特征 | Python | PHP |
|---|---|---|
| 解释型 | 是 | 是 |
| GIL / 单线程 | GIL | 单线程 |
| 主导模型 | 同步 (Django, Flask) | 同步 (FPM) |
| 异步运行时 | asyncio + uvloop | Swoole / True Async |
| 异步框架 | FastAPI, Starlette | Hyperf |
Python 的数据表明,在单线程解释型语言中 转向协程是可行的。收益的规模 取决于工作负载特性,而非语言本身。
参考文献
生产案例
- Duolingo: How We Started Our Async Python Migration (2025)
- Super.com: Quart ASGI, 90% cost reduction
- Instagram: asyncio adoption at scale (PyCon Taiwan 2018)
- Feature Store: Multithreading to Asyncio
- Talk Python: Flask → Quart rewrite
- Microsoft Azure: uvloop as default (2025)
基准测试
- Cal Paterson: Async Python is not faster
- Python Concurrency Model Comparison (2025)
- HTTPX vs Requests vs AIOHTTP (Oxylabs)
- uvloop: Blazing fast Python networking (MagicStack)
- TechEmpower Framework Benchmarks