Spring Boot、Go Gin、FastAPI 到底怎么选?我压测了两轮,最后发现决定因素是运维的 shell 脚本

🔑 关键词:后端框架选型,Spring Boot,Go Gin,FastAPI,框架性能对比

📖 摘要:不排 QPS 表格,聊两个我自己总结的选型指标:故障排查半衰期和逃生舱覆盖率。附 M1 Pro 上的 wrk 实测数字、pydantic v1 升 v2 和 Spring Boot 2.3 升 2.7 的踩坑记录,以及三档团队的具体建议。

去年 11 月我们接了个内部工具的项目,满打满算 20 来个接口,日 PV 撑死两万。开会定技术栈,四个人吵了四十分钟。做 Java 的同事说 Spring Boot 稳,写 Go 的说 Gin 编译一个二进制扔服务器上就完事,我从上家公司带过来的习惯是 FastAPI。最后领导拍板 Spring Boot,理由跟性能无关,也跟生态无关,他说「运维那边只有 Java 的部署脚本,换一个我得重新教一遍」。

图片

这事我后来越琢磨越有意思。我们在网上刷到的框架对比,清一色是 QPS 柱状图、内存占用曲线、GitHub star 数,好像选型是个纯技术活。真到落地的时候,决定权往往在运维的 shell 脚本、HR 能不能招到人、以及上个月离职那位同事留下的代码谁看得懂。所以下面我不打算再排一遍 QPS 表格,那玩意儿你搜十篇文章能看十遍。我想聊两个我自己总结的指标,还有一堆踩坑记录。

先把我实测的数字摆出来,省得有人说我空谈

2023 年我在一台 M1 Pro / 16G 的 MacBook 上做过一轮压测,wrk -t4 -c100 -d30s,接口就返回一个 {"ok":true},不碰数据库不碰 Redis:

Go 1.21 + Gin 大概 9 万到 12 万 RPS。Node 20 + Fastify 在 3.5 万左右。Python 3.11 + FastAPI + uvicorn 单 worker 是 1.2 万上下,多开 worker 能顶上去但 CPU 直接吃满。Spring Boot 3.2 + Tomcat 默认线程池,8 千到 1.5 万。换成 WebFlux 能跑到 3 万,但代码写起来完全是另一个世界,调试体验也另说。

图片

好,现在接上 PostgreSQL,做一个「按主键查一行」的真实查询。数字变成这样:Go 5 千到 8 千,FastAPI 4 千到 6 千,Spring Boot 4 千到 7 千,Node 4 千到 6 千。十倍差距压缩到两倍以内,再加一层 Redis 或者一次跨服务 HTTP 调用,基本就拉平了。这里我要说清楚,这是我一次随手测的结果,不是标准 benchmark,你的机器和网络肯定不一样。

结论很简单:如果一天的请求量不到 1000 万,框架性能这个维度的权重不该超过 10%。你要真到那个量级,先看数据库慢查询日志,再看有没有 N+1,最后才轮到换框架。

指标一:故障排查半衰期

这个词是我自己编的,说白了就是:你在生产环境看到一个报错,从「这什么鬼」到「找到能用的解决方案」平均花多久。

图片

Spring Boot 的报错栈动不动两百行起,BeanCreationException 套 NoSuchMethodError 套三层 Caused by,第一眼看很崩溃。但关键在于,任何一种你能碰到的 Spring 报错,2016 年之前就有人问过了。你把报错第一行粘进搜索框,前三个结果里至少有一个是 StackOverflow 上五十个赞的答案。半衰期短,这是它最大的隐性优势,比什么自动配置都值钱。

反过来一些新的小众框架,报错信息写得很漂亮,一句话告诉你哪里错了。但你搜的时候,GitHub Issues 里只有三条,两条是 me too,剩一条被 maintainer 标了 wontfix。这时候你唯一的选择是读源码,读源码的成本是两小时起步。

我自己具体吃过一次亏。2022 年用 FastAPI 0.75 左右,pydantic 还是 v1。后来项目要升依赖,pydantic 跳到 v2,整个 schema 层得改:orm_mode 变 from_attributes,@validator 变 @field_validator,class Config 变 model_config 字典。我们大概 60 多个 schema 文件,我花了整整一个周末再加一个下午。官方迁移指南写得不算差,但里面对不上实际报错的地方不少——比如 @validator 在 v2 里其实还能用,只是标了 deprecated,可 pre=True 的语义有细微变化,这个坑我是翻到 GitHub issue 才明白的。

Spring Boot 也栽过。2023 年初从 2.3 升 2.7,WebMvcConfigurer 里 addCorsMappings 的一个默认方法行为改了,导致线上所有跨域请求 403,前端那边一片红。报错信息只有一句 CORS preflight did not succeed,完全不提是配置类的问题。最后是在 GitHub 一个 2021 年的 issue 里翻到的。所以半衰期这东西跟框架新旧关系不大,跟「用的人够不够多、够不够久」强相关。

图片

指标二:逃生舱覆盖率

第二个指标是:你用框架的「标准姿势」写到一半发现不行了,能不能干净地绕过去,还是必须把整个框架拆了重来。

Spring 在这块是满分选手,但代价是复杂度。想绕开 JPA 直接写 JDBC?JdbcTemplate 就在那儿。想绕开 Spring Security 自己写 filter?加个 FilterRegistrationBean。想覆盖任何自动配置?写个 @Bean 加 @Primary。逃生舱到处都是,但每扇门上都贴着「你确定要这么做吗」的警告。

图片

Django 的逃生舱算半个。Model.objects.raw() 或者 connection.cursor() 都能用,但你一旦写了 raw SQL,分页、序列化、admin 后台全都得自己接,等于出了舱门发现外面没路。

FastAPI 的逃生舱其实不少,因为它本身建立在 Starlette 和 pydantic 之上,想换校验库、想直接操作 Starlette 的 Request,都能做。问题在于这么写的人少,出事没人陪你。最难受的是那种「框架即世界」的设计,你的业务逻辑必须写成框架规定的形状,想抽出来复用就得跟路由、DI、生命周期绑死。这类框架前三个月开发特别爽,半年后加需求就开始难受,一年后你会在心里骂自己当初为什么图省事。

具体怎么选,我给三档

五个人以下的小团队、项目预期活一到两年、没有专职运维:FastAPI 或者 NestJS。招人便宜,前端转过来一周能上手,部署就一个 Dockerfile。别碰 Spring,那套东西的隐性成本是你要养至少一个懂 JVM 的人——GC 日志、堆 dump、线程池,哪样都得有人接得住。

图片

中等规模、业务要活三年以上、团队里本来就有 Java 底子:Spring Boot 仍然是最安全的选择。不是因为它优雅(它不优雅),是因为「随便换个同事接手都能看懂」的概率最高,而且国内 Java 后端的简历池子比 Node 大十倍不止,你招人的时候会感谢自己。

高并发、接口少、团队小、运维能力还行的:Go。但我得把话说清楚,Go 的优势不是 QPS,是「一个 12MB 的静态二进制扔到任何 Linux 上都能跑」,以及容器里内存占用很稳——一个 Gin 服务常驻 15 到 25MB,同功能的 Spring Boot 大概 300 到 500MB。这个差距乘上 K8s 里的 50 个 Pod,就是每个月账单上的真金白银。

最后

我有个偏见:别在选型阶段纠结超过一周。我们那次开会吵了四十分钟,最后决定因素是运维的脚本长什么样。你花两周做的 benchmark,大概率不如「团队里谁最熟」这一个因素管用。真正的技术债不是选错框架,是在错误的地方花了太多时间做正确的比较。

🏷️ 标签: