Node.js vs FastAPI 事件循环:深入探讨异步并发
发布: (2026年3月5日 GMT+8 05:34)
9 分钟阅读
原文: Dev.to
Source: Dev.to
生产环境中的 API 堆栈
两个堆栈在现代生产系统中反复出现:
- Node.js
- FastAPI
两者都因处理高并发工作负载而广为人知,并且在很大程度上依赖事件驱动架构。
快速对比
| 功能 | Node.js | FastAPI |
|---|---|---|
| 并发模型 | 单线程事件循环 + 非阻塞 I/O | 异步协程 + 多工作进程 |
| 典型运行时 | JavaScript (V8) | Python (asyncio) |
| 跨核心扩展 | 需要 cluster 模式 | 通过多进程自然实现 |
| 最佳适用场景 | I/O 密集型 API、实时应用、WebSocket | API、机器学习推理后端、流式处理、微服务 |
| 潜在瓶颈 | CPU 密集型工作会阻塞事件循环 | 通过使用独立进程避免 GIL |
Node.js
核心理念
- 在 单线程 上执行 JavaScript(与浏览器模型相同)。
- I/O 操作交由 libuv 处理,这是一个高性能的 C 库。
- 当 I/O 完成后,回调会被推回事件循环。
这种设计在 I/O 密集型工作负载下表现极佳,而这正是大多数 Web 服务器的主要场景。
代价是:CPU 密集型工作会阻塞 JavaScript 线程,从而拖慢整个服务器。
重要特性
- 所有 JavaScript 代码都在同一个线程上运行。
- I/O → 交由 libuv 处理(网络、文件系统、计时器、线程池调度)。
- 事件循环阶段:
| 阶段 | 发生的事情 |
|---|---|
| Timers | 执行通过 setTimeout() / setInterval() 计划的回调。 |
| Pending Callbacks | 处理系统级回调(例如 TCP 错误)。 |
| Poll | 循环的 核心 – 等待 I/O 事件,获取已完成的操作,运行回调。 |
| Check | 执行 setImmediate() 回调。 |
| Close Callbacks | 处理清理事件(例如 socket.destroy())。 |
线程池(libuv)
- 负责文件系统访问、DNS 查询、压缩、加密等任务。
- 默认大小: 4 个线程(可通过
UV_THREADPOOL_SIZE配置)。 - JavaScript 的执行仍然是单线程的 – 重 CPU 任务仍会阻塞循环。
简单示例(Node.js)
// Callback style (old)
db.query(sql, (err, result) => {
if (err) throw err;
console.log(result);
});
// Promise / async‑await (modern)
const result = await db.query(sql);
console.log(result);
即使使用
async/await,Node 仍然是通过回调队列调度操作的;该语法只是对 Promise 和回调的语法糖。
Source: …
FastAPI
核心理念
- FastAPI 只是一个框架;运行时来自 ASGI 服务器,例如:
uvicornhypercorngunicorn + uvicorn workers
- 每个 worker 是:
- 一个独立的操作系统进程
- 它自己的 Python 解释器
- 它自己的事件循环
因此并发来源于 两层:
- 每个 worker 内部的 异步协程。
- 跨 CPU 核心的 多个 worker 进程。
典型端点
from fastapi import FastAPI
app = FastAPI()
@app.get("/users")
async def get_users():
result = await db.fetch_all()
return result
- Python 会创建一个 协程对象(可暂停的计算)。
- 事件循环 调度 并 恢复 协程,根据需要执行。
- 与回调地狱相比,这提供了 线性、可读 的编程模型。
回调 vs 协程模型
| 方面 | Node.js(回调) | FastAPI(协程) |
|---|---|---|
| 语法 | db.query(sql, (err, result) => {...}) | result = await db.query() |
| 流程 | 嵌套回调 → “回调地狱”(可通过 Promise/async‑await 缓解) | 可暂停函数 → 自然流程 |
| 调度方式 | 回调队列(事件循环) | 协程的协作调度 |
基于进程的并行
- FastAPI 部署 通常运行 多个 worker 进程:
- 每个 worker → 1 个 OS 进程 → 1 个 Python 解释器 → 1 个事件循环
- 这实现了跨 CPU 核心的 真正并行,规避了 Python 的全局解释器锁(GIL),因为每个进程都有自己的解释器。
Worker 1 → CPU core 1
Worker 2 → CPU core 2
...
并发策略概述
Node.js 并发模型
- 单线程事件循环 + 非阻塞 I/O。
- 擅长:
- APIs
- 流媒体服务
- 实时应用(WebSockets、聊天服务器、实时仪表盘)
- 优势:
- 高 I/O 吞吐量
- 实时系统
- 巨大的生态系统与成熟的工具链
- 劣势:
- CPU 密集型任务会阻塞循环(例如,大型 JSON 解析、图像处理、加密、机器学习推理)
- 需要 worker 线程 或独立的微服务来处理重 CPU 工作。
FastAPI 并发模型
- 异步协程 + 多工作进程。
- 即使某个工作进程被 CPU 工作阻塞,其他进程仍可继续提供服务,从而实现响应式处理。
- 优势:
- 与机器学习框架无缝集成
- 通过 Pydantic 提供强类型提示和自动验证
- 出色的开发者体验
- 对 I/O 密集型和 CPU 密集型工作负载均表现优异(通过进程扩展)
- 常见误解:FastAPI 并不会自动将 所有 内容异步化;必须显式使用
async def并在 I/O‑bound 调用前使用await。
要点
- Node.js 在工作负载为 I/O‑密集 且能够保持 JavaScript 执行轻量时表现出色。
- FastAPI 为 混合工作负载 提供了更灵活的模型,利用 Python 丰富的生态系统和进程级并行。
- 选择与您的 性能特征、团队专长 和 运营约束 相匹配的技术栈。
澄清 FastAPI 中的异步端点
声明: “那不是真的。”
实际情况
def endpoint():
...
- 这 不是 异步的。
- FastAPI 会在 线程池执行器 中运行它。
结果:
阻塞函数 → 在线程池中执行 → 事件循环保持空闲
若要直接使用事件循环,端点 必须 声明为:
async def endpoint():
...
理解全栈层
Node.js
V8 Engine → Node.js Runtime → libuv → Operating System
- V8 执行 JavaScript。
- libuv 处理异步 I/O。
FastAPI
Python Interpreter → FastAPI Framework → ASGI → Uvicorn Server → asyncio / uvloop → Operating System
- 许多 FastAPI 部署使用 uvloop,这是一种用 C 编写的事件循环实现。
- uvloop 本身是基于 libuv 构建的——与 Node.js 使用的同一库。
产生的等价关系
FastAPI + uvloop = Python + libuv
不同的语言,却使用相同的底层异步引擎。
并发哲学
Node.js 和 FastAPI 均遵循相同的基本哲学:
- 事件驱动、非阻塞 I/O
但它们在并发扩展方式上有所不同。
| Feature | Node.js | FastAPI |
|---|---|---|
| Execution model | Single JavaScript thread | asyncio coroutines |
| I/O handling | libuv thread pool | asyncio / uvloop |
| Parallelism | Cluster mode or worker threads | Multiple worker processes |
| Multi‑core scalability | Requires explicit clustering | Natural multi‑core support |
实用指南
- 使用 Node.js 当构建实时系统并且希望保持在 JavaScript 生态系统中时。
- 使用 FastAPI 当围绕 Python、数据科学、机器学习或 AI 工作负载构建服务时。
关键要点: 了解每种并发模型在负载下的表现至关重要,因为早期的架构决策会决定系统后续的可扩展性。
您的想法
您更喜欢哪种运行时用于高并发 API,为什么?
进一步阅读
如果您喜欢这篇文章,可以在此阅读我的更多博客: