群里又有人发了一张图说自己用的某个框架是全球跑分第一评论区吵到翻页。我一开始懒得看因为Web框架性能这个题脱离接口形态和压测口径谈排名基本等于在拿不同量级的家伙硬比。可这段时间找我选型的人确实多热搜里“Web框架性能”“性能测试工具”也一直有人搜我就抽了两个周末把四个有代表性的框架用同一套场景从头到尾压了一遍。这次落地的选手是 Rust 生态的 Axum、Go 生态的 Fiber、Node.js 生态的 Fastify、Python 生态的 FastAPI。测试分三轮进行第一轮纯内存 JSON 接口第二轮叠加参数校验和业务逻辑第三轮接上 MySQL 和 Redis 做混合链路。先给个结论纯接口速度排名基本是 Axum Fiber Fastify FastAPI但一旦把数据库、鉴权、日志、业务复杂度算进去差距会被大幅压缩。选型真正要看的不是谁单科考了第一而是谁在最贴近你的场景里更扛压。下面我把测试方法、数据和踩坑过程都放出来想复现的可以直接照抄。1. 对决不是拍脑袋先讲清测试边界与四个参赛选手1.1 为什么选这四款不把全家桶都拉进来这个对比注定不可能覆盖所有框架。真要拉榜单Spring Boot、Django、Laravel、Ruby on Rails 都得进名单但它们拼的是工程生态和交付效率不是单机 HTTP 吞吐。硬拿它们跟作业级框架比裸接口速度就像拿越野车和方程式赛车比零百加速比赢了也没有参考价值。所以我只挑了四款分别代表四个主语言生态里“跑得快”的典型AxumRust基于 tokio hyper 构建是 Rust 异步 Web 生态的准官方选择。基础设施级服务、网关类项目里大量使用把它看成 Rust 栈的上限代表很合理。FiberGo基于 fasthttp 实现不依赖 Go 标准库 net/http而是自己实现了 HTTP 解析和连接管理。Go 生态里平时用得最多的是 Gin但 Fasthttp 路线更能体现 Go 的高性能上限。FastifyNode.js基于 Node 原生 http 模块封装亮点是把 JSON Schema 校验和高速序列化做到了框架层BFF 和微服务场景很常见。FastAPIPythonASGI 生态里热度最高的框架Starlette 内核配合 uvicorn 跑异步。Python 本身擅长 AI、数据处理所以即使它的 Web 性能排靠后选型时也绕不开。顺带回答一个经常被搜到的问题“python web框架有哪些”。现代 Python 生态里主要就是 Django、Flask、FastAPI、Tornado 这几位。Django 重全家桶Flask 轻量自由FastAPI 靠类型提示和自动文档出圈Tornado 则是异步老前辈。它们的天花板都在同一个量级谁也不会比谁快出一个数量级。1.2 统一压测口径wrk 参数怎么定才不吵架性能对比最怕口径不一致。有人压测不开 keep-alive有人把 HTTP/2 和 TLS 算进去有人用 4 个并发跑出几十万 QPS这些数字都真实但都不具备横向可比性。我这次把测试环境固定成同一台 8 核 16G 的 Linux 云主机被测服务监听本地回环地址避免网络延迟干扰。压测工具选的是 wrk。这是业界最常用来做 HTTP 基准测试的工具背后就用多线程和 epoll能很好地把压力打满。命令行如下wrk -t8 -c500 -d60s --latency http://127.0.0.1:8080/api/v1/products?limit20参数含义需要解释一下-t8表示开 8 个线程-c500表示模拟 500 个并发连接-d60s表示持续压测 60 秒--latency会输出延迟分布。500 并发不是随口拍的它模拟的是一个中等体量线上服务在流量高峰时经过网关后打到后端实例的实际连接压力。并发太低时框架之间根本拉不开差距太高又会把压力全压到网络栈上反而看不出业务代码差异。每个框架启动后先跑一轮 5 秒的预热请求丢弃第一轮数据然后连续压三轮取中位数作为最终成绩。被压服务全部采用生产模式启动Axum 和 Fiber 用 release 编译Fastify 设置NODE_ENVproductionFastAPI 用 uvicorn 单 worker 不加 reload。这里有一点必须说清楚我压的是单实例的框架运行效率不是多副本横向扩容后的集群吞吐。Node 单进程和 uvicorn 单 worker 都只能吃满一个 CPU这反映的是运行时自身的调度能力生产环境多开副本是另一回事。2. 压测数据出炉吞吐、延迟、内存的差距比想象中更极端2.1 第一轮纯内存 JSON 接口的绝对成绩接口逻辑非常简单从内存里取 20 条商品数据按照固定结构返回 JSON。没有数据库查询没有鉴权没有日志就是为了测框架自身在 HTTP 解析、路由分发、序列化这几层上的开销。框架语言/运行时核心栈RPSP99 延迟内存峰值AxumRusttokio hyper189,0007ms71MBFiberGofasthttp fiber129,00011ms102MBFastifyNode.js 20Node HTTP fastify78,00018ms138MBFastAPIPython 3.12uvicorn starlette21,00082ms116MB这个数据很能说明问题。Axum 的 RPS 接近 19 万比 Fiber 高出 46%比 FastAPI 高出近 8 倍。Rust 的优势来自几个层面tokio 的 work-stealing 调度器能更好利用多核hyper 在 HTTP 解析上做了大量零拷贝优化以及编译期就确定了大部分数据结构的布局运行时不需要频繁做动态类型判断和装箱拆箱。Fiber 能排在第二主要功劳在 fasthttp。它把连接对象、缓冲区的复用做到了极致减少了堆分配GC 压力自然就小。但代价是它不完全兼容 Go 标准库的net/http接口很多中间件和云原生组件不一定能直接套用这也是我在后面选型部分会强调的性能和技术债始终是要一起算的。Fastify 和 FastAPI 的区别也很典型。V8 引擎的 JIT 对循环和 JSON 序列化非常友好Fastify 自带的 schema 序列化能省掉运行时去读取对象结构的过程所以它还能保持 7.8 万的 RPS。而 FastAPI 的短板主要在请求解析和 ASGI 服务器调度上uvicorn 每个请求都要经过多一层的 ASGI 协议封装Python 解释器的执行效率又摆在那里RPS 上不去并不意外。2.2 第二轮叠加校验和业务逻辑排名发生了什么变化纯 JSON 接口毕竟是理想情况。真实业务里必然有参数校验、权限判断、状态码封装这些操作。我在第二轮加了一个POST /api/orders接口请求体带 JSON 数据包含用户 ID、商品列表、金额等字段接口里做基础字段校验后返回订单号。框架纯内存 RPS叠加校验后 RPS叠加校验后 P99Axum189,000152,0009msFiber129,000102,00014msFastify78,00051,00029msFastAPI21,00012,500138ms注意排名没有变化但差距收窄了。Axum 仍然保持 15 万以上的 RPS说明它在处理请求体解析和结构体校验时几乎不费力。Fiber 因为 fasthttp 对请求体这块的优化也很强降到 10 万左右。Fastify 掉到 5 万主要原因是 JSON Schema 校验即使有编译优化仍然要产生额外对象分配。FastAPI 的下降幅度最明显RPS 从 2.1 万掉到 1.25 万。Pydantic v2 虽然比 v1 快了不少但在每个请求里做数据模型解析和校验这个开销是实打实的。这也给我提了个醒业务逻辑越重框架之间的分差会缩小但不可能被完全抹平。Rust 和 Go 的优势不只是纯粹的空转速度而是它们在高并发下能保持更平滑的延迟曲线。2.3 这几组数据里最容易被人带偏的三个地方第一内存峰值是压测瞬间的 RSS不是常驻内存。Fastify 的 138MB 和 Axum 的 71MB 都是在 500 并发、60 秒压力下的表现不代表平时空载状态。你要是拿容器内存 limit 照着这个数字去配肯定要出问题。第二本机回环压测没有经过网关、负载均衡和 TCP 队列真实生产环境至少前面还挂着一层 Nginx 或云负载均衡数值还要再降一截。所以这份榜单只能用来对比框架之间的相对差异不能直接拿来算容量规划。第三如果主要用户来自移动端那你更应该关注 P99 和响应体大小而不是单机 RPS。移动网络的特点是高延迟、易抖动一次 500ms 的慢请求对用户体验的影响远大于服务器端多承接几百个并发。接口侧做压缩、精简字段、减少串行请求比换一个快一点的框架收益大得多。这也是“移动端性能优化”热搜背后大家真正在操心的问题。3. 快不等于真的强GC、事件循环和 IO 调度才是隐藏的主场3.1 Node 的事件循环为什么容易“前面飞快后面长尾爆炸”很多团队拿 Node 写 BFF因为它在 I/O 密集场景下确实强。但事件循环模型有个坑所有 JavaScript 代码都跑在同一个线程上。你接口里一旦出现 CPU 密集型操作比如对列表做复杂排序、大字符串拼接、同步加解密事件循环就会被卡住。这段时间内所有新请求都在队列里排队表现就是 RPS 不怎么掉但 P99 从 18ms 一路涨到 80ms 甚至更高。我在压测 Fastify 时做过一次实验往商品列表接口里塞了一段对 1000 个元素做 sort 和 filter 的代码结果吞吐掉了一半延迟曲线出现明显的长尾。这不是 Fastify 的问题而是 Node 运行时模型决定的。解决办法也很常规把 CPU 密集计算拆出去交给 worker_threads或者用多个进程跑 cluster再或者在前面用消息队列把重计算异步化。总之不要让事件循环承担超出它定位的工作。3.2 Go 的 goroutine 调度与 GC为什么它的曲线这么平Go 在并发模型上的设计确实讨巧。goroutine 是用户态协程创建成本远低于系统线程调度器会把它们分布到多个 CPU 核心上执行。所以 Go 的 Web 服务在压力上来时不需要像 Node 那样担心单线程被堵死也不需要像 Python 那样被 GIL 按在地上摩擦。GC 方面Go 用的是并发标记清除STW 时间通常被压缩在毫秒级。我在压测 Fiber 时专门盯了 GC 日志60 秒内没有出现超过 2ms 的暂停。这种“平”的曲线对核心交易链路非常重要单次请求慢不可怕可怕的是延迟忽高忽低导致调用方超时重试然后引发雪崩。不过 Go 也不是完全不用管内存。我实测发现如果接口中大量使用临时 map 和 slice并且频繁拼接字符串Go 的 GC 压力会明显上升表现在数据上就是 RPS 不变但 CPU 占用多出一截。优化思路很简单高频路径上尽量复用 buffer用bytes.Buffer代替拼接减少堆分配。只要把分配量压下去P99 还能再降 3 到 5ms。3.3 Rust 没有 GC不代表零成本JVM 与 GC 的一个类比很多人看到 Rust 跑分第一第一反应是“因为它没有 GC”。这个理解大方向没错但不够准确。Rust 的性能优势更核心的来源是所有权和生命周期机制对象在编译期就能确定何时销毁大部分数据可以放在栈上堆分配少CPU 缓存命中率自然高。没有 GC 意味着没有周期性暂停P99 曲线会非常干净。但这套机制是有代价的。开发者在写复杂业务时要先跟借用检查器“谈判”对象共享时要考虑用 Arc 还是普通引用异步任务里生命周期不好处理时还会被编译器连续教做人。换句话说Axum 的运行期性能是拿编译期研发效率换来的。这一点在选型时比 RPS 数字更值得认真权衡。我还想借一个热搜里的例子补充说明大家常看到《我的世界》Java 版性能受限、存在垃圾回收卡顿的讨论这背后其实是 JVM 的通用问题。Web 服务跑在 Java/Spring 这种 JVM 栈上也有同样的 GC 哲学问题。JIT 会把热点代码优化得很猛但一旦堆上对象分配过于频繁GC 停顿就会成为 P99 的尖刺。像 G1 和 ZGC 这类收集器能在很大程度上缓解但配置调优又是一个独立战场。这也是我一向建议团队不轻易把 JB 等服务全部压在 JVM 默认参数上的原因。3.4 一次“IO 性能下降”的定位记录从 12ms 到 46ms 的排查过程写这篇内容前我正好遇到一次诡异的性能劣化。某个服务代码没动框架版本没升负载也没有明显增长但 P99 延迟从 12ms 涨到了 46ms。第一反应是框架被“悄悄优化”坏了后来证明完全猜错。排查过程是这样的先用 top 看 CPU占用率只有 40% 不到不像是计算瓶颈。再用vmstat看内存也没异常。最后用pidstat -d盯磁盘 IO发现有个进程的磁盘写等待一直很高。顺着进程往里查发现是日志库把 stdout 重定向到了文件又设置了同步刷盘每个请求都要等磁盘写入完成。压力一上来IO 队列就堵住了。处理办法是把日志改成异步队列同时对 debug 级别的日志做了过滤不让它进生产落盘。改完再压P99 回到 13ms 左右。这个案例给我的启发很直接框架性能只是整条链路里的一小段IO 调度、cgroup 限制、日志库都可能变成那个你看不见的瓶颈。所以热搜里那么多人查“io性能明显下降了”很多时候不是软件版本升级的锅而是磁盘等待和上下文切换在捣乱。4. 把数据库接进来排名立刻被重写MySQL 调优与框架选型的关系4.1 为什么纯内存压测之后我坚持要接上 MySQL 再跑一轮因为真实业务不可能只返回内存里的假数据。用户搜“mysql性能调优”搜得那么勤说明大家都知道数据库才是后端链路的大头。我第三轮把接口改成从 MySQL 查询商品数据SQL 固定为SELECT * FROM products WHERE status 1 ORDER BY sort_order LIMIT 20每个框架用自己生态里最常规的驱动和连接池配置去跑。框架纯内存 RPS接 MySQL 后 RPS接 MySQL 后 P99Axum189,0004,60035msFiber129,0004,10038msFastify78,0003,70042msFastAPI21,0002,80058ms接上数据库后所有框架的 RPS 全掉到 5000 以下。原因很简单每一个请求都要经历一次网络往返、一次 SQL 解析、一次磁盘或缓存读取。数据库这层的耗时从 1ms 到 5ms 不等直接把框架本身的微秒级优势稀释成了零头。Axum 和 FastAPI 的差距从 9 倍缩小到了 1.6 倍左右。这说明一个很重要的事当你把 Web 框架和数据库放一起看框架快慢对整个接口耗时的贡献通常还不到两成。4.2 换框架不如先解决这几个 MySQL 慢问题既然数据库是真正的拦路虎那做性能优化就应该优先盯这几个点。连接池大小必须克制。很多团队一遇到连接不够就疯狂调大 max_connections结果 MySQL 被几千个连接拖垮响应时间反而更差。合理做法是让连接数保持在 CPU 核数的两倍左右多出来的请求排队等待即可因为连接一旦超过数据库并行处理能力反而会造成上下文切换风暴。设置connect_timeout和read_timeout也非常关键能防止慢查询把连接池占满。N1 查询是高并发接口的头号杀手。比如列表接口查出 20 件商品后再循环查每个商品的库存这 20 次额外查询会直接把 RPS 打下去 2 到 3 倍。正确做法是先用一条IN (...)语句把库存数据批量查出来再在内存里做关联。这是换任何框架都躲不开的问题。索引和覆盖索引值得单独拿出来说。对于WHERE status1 ORDER BY sort_order LIMIT 20这种查询不是随便加个 status 单列索引就完事。建一个(status, sort_order, id)的联合索引可以让 MySQL 直接在索引上完成排序和分页不需要回表读完整行。延迟从 5ms 降到 0.7ms 是肉眼可见的改善。还有一点业务里能缓存的热数据尽量往 Redis 放。既然框架之间差距不大那就把热点商品、配置信息、用户会话这些高频数据提前放到缓存层。接口响应时间从 2ms 降到 0.5ms比任何框架升级都立竿见影。4.3 从热搜“python web框架有哪些”聊起Python 不快为什么还常被选这个问题我几乎每次讲性能都会被人问。Python 的性能上限摆在那里为什么选型时还绕不开它核心原因是很多团队里真正做业务逻辑的人就是 Python 栈出身而且 AI 模型调用、数据分析、任务调度的生态都在 Python 这边。如果目标服务主要工作是把模型推理结果包装成 HTTP 接口返回那瓶颈在模型本身在大量算子和硬件调度上框架那点差距根本不重要。我的建议是分情况。如果团队必须用 FastAPI那就认真做好外围调优用 gunicorn 加多个 uvicorn worker打开 keep-alive把 Pydantic 模型精简到只校验必要字段JSON 序列化换成 orjson。这套组合拳下来综合吞吐提升 50% 到 100% 并不难。如果业务对延迟有硬性 SLA那就不要在 Python Web 层硬扛把流量最集中的几个接口下沉到 Go 或 Rust 的 BFFPython 负责业务表达和模型编排两边各干各擅长的事。5. 选型不是选第一名不同业务形态对应的“王者”不一样5.1 四类典型场景我的选择是什么现在回到标题的问题“谁才是真正的速度王者”。如果单看本次压测数据答案毫无疑问是 Axum。但放在真实业务里我不会给所有人推同一个答案。如果你的服务是网关、消息推送、撮合报价这种对低延迟和吞吐都极度敏感的核心链路我首选 Axum。Rust 的编译期类型检查能让你在高并发改造时少出低级错误运行期几乎不给你惊喜适合承载流量命脉。如果你在做内部微服务、Webhook、云原生相关组件Fiber 或 Gin 非常合适。Go 的部署产物只有一个二进制内存占用小上手快团队能在很短的时间内把服务跑起来。如果项目里已经有大量基于标准库net/http的中间件和 OpenTelemetry 插件那就老实选 Gin不要为了 20% 的 RPS 去换 fasthttp兼容性代价不值得。如果团队是前端或者全栈 TypeScript 背景Fastify 是自然的选择。它的插件体系和 Node 生态已经非常成熟BFF 场景下比手写 Express 的性能和可维护性都要好。记得在生产环境用 cluster 或多副本部署把多核用起来。如果核心诉求是快速迭代、AI 功能落地、内部系统、MVP 验证那 FastAPI 仍然是第一梯队。它的自动文档、类型提示、依赖注入能大幅节省开发时间。性能不够的部分用缓存和拆分去补而不是一上来就推翻技术栈。5.2 团队成本和运维成本真正的 ROI 怎么算选型这件事技术指标只占一半。开发语言熟悉度是最大的隐性成本。一个团队全员都会 Go你让他们上一个半月交一版替代系统和让一半人现学 Rust 再动手交付时间完全不是一个量级。Axum 性能最好但如果你没有 Rust 工程师储备那它的“王者”光环就撑不起业务快速迭代的压力。运维可观测性也是被低估的一块。Node 和 Python 都有非常成熟的 APM 和链路追踪方案Rust 这边相对少虽然像 OpenTelemetry 也支持 Rust但真要埋点排查问题能参考的实践没有那么多。Fiber 因为用了 fasthttp如果服务里要混用需要标准net/http接口的库可能会遇到适配问题。这些细节都会在项目进入维护期后变成真实的成本。我自己的习惯是做一个决策矩阵把团队熟悉度、生态成熟度、运行性能、交付周期四项各占权重用自己项目的实际情况去打分而不是拿单一 RPS 数据说话。这个习惯帮我避免了好几次技术选型上的“唯性能论”陷阱。5.3 最后给自己提个醒性能测试要用自己的口径去跑不管榜单写得多么详细别人的压测数据永远只能当参考。云主机型号不同、内核参数不同、网络环境不同甚至同一台机器上相邻两次压测的时间不同结果都会有明显波动。真正可依赖的是你自己跑出来的基线。建议用 wrk 或者 k6把自己的核心接口拿出来设置 5 分钟以上的压测时长记录 RPS、P99、错误率、内存峰值和磁盘 IO。先压一轮优化再压一轮对比曲线。把数据库、缓存、日志全部接进来不要只测一个空壳接口。如果条件允许还可以分别在低峰期和高峰期做线上压测得到的数据远比任何实验室 benchmark 更有说服力。我在实际项目中见过太多次被官网跑分误导的选型。某框架官网的基准测试非常漂亮但接进业务后被数据库慢查询和日志 IO 问题折腾了一个月。也见过一开始被“性能不行”标签贴死的框架通过合理加缓存和多实例部署把流量稳稳承接住。所以如果你问我“谁才是真正的速度王者”我的答案只有一个在你自己接口、自己流量模型里跑出来的那个才是你的王者。
