
高并发这个词圈内聊了十年从早期的“加机器硬扛”到现在的“框架选型定生死”思路已经完全变了。我最近正好完成了一个消息推送中台项目的技术选型与压测复盘整个过程的核心就一句话别神话任何框架用性能数据说话。这篇文章不聊虚的直接把我对比主流框架、梳理性能数据、最终做出技术决策的完整过程拆给你看包括踩过的坑和排障实录希望能给正在做类似高并发项目选型的朋友一些参考。1. 性能数据先行把“高并发”拆成可测量的指标接到这个项目需求时第一反应不是去论坛上问“哪个框架抗压”而是把“高并发”这三个字拆解成团队能统一认知的具体指标。很多选型翻车根源就在于大家对“高并发”的理解根本不在一个维度上。1.1 业务场景决定指标维度我们这个项目是典型的高并发im场景下的消息推送中台峰值要求是单机支撑10万级长连接同时保证消息推送延迟在毫秒级。这个场景下**并发连接数、吞吐量TPS/QPS、请求延迟P99、错误率、资源消耗CPU/内存**这五个指标是核心。如果只是做一个门户网站可能QPS上千就够用了但IM场景完全是另一个量级。这里有个关键认知误区很多人把“框架能支撑多少QPS”等同于“高并发能力”这是不全面的。对于IM、实时通信这类场景连接数上限C10K/C100K问题和长连接下的内存管理往往比单纯的QPS更致命。所以我在和团队沟通时明确了一个原则先算业务账再选技术栈。业务账算不清楚后面的性能数据都是无源之水。1.2 硬性指标与软性架构约束除了硬性性能指标架构演进方向也必须纳入决策范围。我们当时的技术栈里JavaSpring Boot负责业务APIPythonFastAPI负责部分AI模型推理服务C负责底层音视频转发。新产品的中台服务就必须考虑与这些既有服务的异构交互成本。因此选型前我从三个维度锁定目标框架候选名单开发效率团队技术栈匹配度、运维成本易部署、易监控、性能上限在目标硬件上能否满足核心指标。基于此最终进入压测对比名单的是Golang的gnet与net/http协程模型、Java的Spring Boot传统虚拟机模型、Node.js的Fastify事件驱动模型、Python的FastAPI异步模型。C暂时排除理由是开发周期长团队熟悉度低除非其他方案全部不达标否则不作为首选。2. 压测方法论没有控制变量的对比都是耍流氓选型压测和常规功能测试完全是两码事。我在这次压测前反复向团队强调一个原则压测的目的不是证明某个框架牛而是找出在极端情况下谁先扛不住以及为什么扛不住。2.1 环境与工具链准备这次压测我没有直接使用云服务器而是用了一台物理服务器32核64G内存做服务端另一台同样配置的机器做压测端原因是物理机可以避免虚拟化层的性能干扰数据更纯粹。压测服务部署在相同硬件、相同操作系统CentOS 7.9上避免系统差异。压测工具选择了wrk和ghzgRPC压测。wrk配合Lua脚本可以模拟复杂场景ghz则专门压gRPC接口。这里有一个小提醒压测时一定要用多线程压测工具避免压测机自身成为瓶颈。压测机即使是32核当目标框架达到1万QPS时也可能耗尽CPU导致结果失真。所以我会同时观察压测机和被测机的资源占用。2.2 场景脚本与样本量设计我设计了三个压测场景场景一短连接高并发接口模拟大量客户端获取Token、登录、发送消息等典型HTTP请求。场景二长连接稳定性压测模拟WebSocket连接建立后保持在线占总连接数的80%持续压测30分钟以上观察内存泄漏和连接数上限。场景三混合流量压测模拟真实业务中读多写少的情况其中读操作占比80%写操作占比20%。在正式压测前我会先跑一个30秒的小流量预热让JIT等运行时优化生效尤其是Java和Node.js再开始正式的5分钟压测。每组测试我会跑3次取中间值避免偶发抖动对数据的影响。3. 框架PK实录四个阵营的代表性选手压测数据我直接做了个对比表数据是在上述物理机上跑出来的取P99延迟和吞吐量的中间值。需要注意的是这个绝对值只代表这类业务场景和硬件条件下的表现你可以在自己的设备上跑一次作为对比。框架QPS场景一/短连接P99延迟场景一长连接数上限场景二CPU占用内存占用2万连接时Go (net/http)52,0008ms约8万出现GC压力多处在一个核心的80%-120%之间浮动约2.1GBGo (gnet)48,0009ms约12万未出现明显GC压力相对更稳定约1.3GBJava (Spring WebFlux)45,00012ms约6万受内存池限制整体占用高多核心波动约3.5GBNode.js (Fastify)38,00015ms约10万受libuv线程池影响单线程吃满多核不均约2.8GBPython (FastAPI)15,00032ms约4万受GIL和IO模型限制多个进程吃满不可控约3.9GB3.1 Go协程生态与高并发的天然契合先说结论在这次压测中Go是综合表现最均衡的。net/http在短连接场景下QPS表现非常亮眼达到5.2万主要得益于goroutine的轻量调度。它的P99延迟也很低只有8ms非常适合业务网关这种短平快的接口。但长连接场景下net/http表现反而不如gnet。我个人分析根本原因在于net/http底层使用的是标准库的网络模型对海量长连接的内存占用优化不到位GC压力变大推动了P99升高出现了高的应用层尾延迟。而gnet基于epoll事件驱动线程模型更可控在长连接场景下优势明显2万连接时内存只用了约1.3GB比net/http少了近40%。这里需要提一下搜索热词里常见的疑问“高并发c和高性能c的区别和关联是什么”。C的高并发通常依赖非阻塞IO和协程库如libco而高性能则侧重于极致延迟和CPU指令优化。两者关联也在此次Go对比中有所体现Go的协程模型本质上是语言层面实现了类似的“轻量级并发单元”只是牺牲了一部分底层硬件控制权换来了开发效率。如果项目核心是自研高性能网关且团队能驾驭C可以参考云厂商的做法但对于我们这种需要快速交付的中台项目Go的性价比显然更高。3.2 JavaSpring Boot生态的助力与掣肘Java的Spring Boot是很多公司和团队的默认选择因为我们太熟悉Spring生态了业务开发效率极高。但压测数据很客观在Netty和Spring WebFlux加持下虽然能跑到4.5万QPS但P99延迟到了12ms是一众框架中最高的而且内存占用同样偏高2万个连接时已经吃掉了大约3.5GB内存。这个结果我很熟悉Spring Boot在性能和内存消耗上确实存在相对较高的固定开销。大量的Bean管理、动态代理、ORM映射在业务代码里带来的便利在高并发下都会转化为额外的计算和内存分配。如果你的系统单实例内存吃紧或者对P99延迟有要求比如低于10ms那么纯Spring Boot方案需要更细致的优化。不过有一点值得肯定Spring Boot的生态成熟度非常高mysql高并发解决方案、mybatis-plus优化、消息队列集成等方案一搜一大把团队任何问题都能快速找到解决办法。所以对于非极致性能要求的业务系统Java依然是稳妥的选择。3.3 Node.jsFastify表现与使用门槛Node.js的结果有些意外也很有意思。Fastify在短连接场景下QPS达到3.8万P99延迟15ms在长连接场景下能撑到10万连接。这说明基于libuv的事件循环模型在IO密集型场景下的确有自己的优势。但它有一个很典型的缺陷单线程模型在压测时暴露了多核利用不均的问题。当压测客户端并发打到2万以上时Node.js的单个CPU核心被打满其余核心闲着即便有cluster模块也不好使。Node.js的cluster模式通过多进程来利用多核但进程间通信的开销会随着连接数的增长而急剧增加。如果你选择了Node.js就要接受它需要一个更完善的进程管理策略比如PM2配合cluster模式并且业务代码里必须严格避免阻塞事件循环的同步操作像文件读取、复杂的CPU计算都应该扔到子进程或Worker Threads里。否则性能数据会惨不忍睹。3.4 PythonFastAPI的定位需清晰把FastAPI放在这个对比里其实有点“欺负人”因为Python的灵活动态属性和GIL决定了它不适合作为核心的通信层。但现实中很多团队为了快速验证AI大模型接口会直接用FastAPI扛流量所以压测数据很有参考价值QPS直接掉到了1.5万P99延迟32ms内存占用却是最高的。FastAPI的优势在于开发速度极快代码量少这一点在验证业务模型时非常爽。但如果你希望用它做高并发网关就得直接放弃了。压测数据反馈得很明显即使挂上Uvicorn的多worker进程GIL也会成为瓶颈。所以我的结论是在整体架构里Python可以作为一个业务Agent层或AI模型接口层存在但流量入口绝对不能是它。如果你看到网上的文章用FastAPI压测出几万QPS请先确认他是不是用了异步数据库客户端并且绕开了GIL的束缚。4. 数据解码与决策不执念于性能最优而是整体架构最优压测数据出来后团队里出现了两种声音一种主张用gnet全栈替换性能和资源占用确实亮眼另一种认为Java生态太成熟应该对Spring Boot进行优化。我的决定是放弃gnet做业务整体框架选择以Go的net/http或基于此的gin框架为主框架同时针对长连接模块单独引入gnet组件。这个决定在团队内部都觉得很费解但数据背后的解释其实顺理成章。4.1 为什么不用性能最高的gnet作为主框架“既然gnet性能最牛为什么不用它作为主框架”这是选型评审时我被问到最多的问题也是最关键的技术决策点。原因是生态缺失gnet非常底层但它没有内置的路由、中间件、参数绑定器、Session管理等Web开发基础设施。我需要自己封装一套Web开发框架这个开发工程量非常大而且很难在短期达到gin这种成熟框架的稳定性。维护风险团队对gnet内部源码的理解不够深。一旦压测阶段没有暴露出的问题在线上爆发排障成本会变得极其高昂。合理分工长连接场景是gnet的强项。一个网关里80%是长连接20%是HTTP接口。我可以做主业务HTTP接口用gin基于net/http而长连接Gateway部分用gnet。这样既能兼顾生态和开发效率也能保证性能充裕。4.2 技术与成本之间的平衡从成本角度看Java重优化方案反而可能是最贵的。即使我对Spring WebFlux做静态化、线程池隔离等优化也无法解决JVM原生内存占用高的底子问题。假设我要支撑20万长连接单机最多撑6万连接意味着我至少需要4台高配机器而选择Go单机支撑12万连接可能只需要2台机器。这里机器成本差异极大尤其是物理机房托管模式下机位、电量、带宽都是真金白银。所以技术决策很多时候并不是选性能最高的选项而是选性价比最高的选项。性能数据只是参考的一个维度成本和团队维护能力是另外两个同样重要的维度。如果你的团队是全栈Java班底那直接选择Java优化反而可能是成本最低的方案如果你们不缺运维经验和C高手全C自研网关也可能是一个选项。我的建议是请务必先算清团队的人力账和机器的资源账再结合压测数据做决策。5. 压测避坑与排障实录那些文档里不写的经验压测过程中有一个很有意思的现象压测数据不达标原因往往不在框架本身。我复盘了一下超过一半的问题是配置、环境或者测试方法的问题。5.1 典型的“框架背锅”问题第一个典型问题是端口号耗尽。我们刚开始用wrk高并发打长连接发现连接数到了3万左右就开始疯狂报错服务端并没有异常。排查了半天发现问题出在压测机的本地端口范围上。Linux默认的临时端口范围是net.ipv4.ip_local_port_range 32768 60999大约是2.8万个端口。当压测机到被测机的一个IP一个端口产生连接时这个数量就卡住了上限。这个问题不该让框架背锅把压测机上的端口范围调宽或者用多个压测机同时压就能解决。第二个典型问题是文件句柄限制。系统默认的文件句柄数是1024当连接数超过这个数字时服务端会直接拒绝连接。所以压测前一定要先调大ulimit -n并且在压测长连接场景时把服务端进程的RLIMIT_NOFILE也改大。这个操作简单但很多第一次压长连接的人都会踩。第三个问题是全局GC算法与JVM参数不一致Java场景。我们压测Spring Boot时有一组数据内存波动特别大P99延迟飙到了几百毫秒。排查后发现是测试环境里JVM参数配置一个用G1一个用CMS导致切换后数据全乱了。这也是老生常谈了压测前必须统一版本和参数。我在这里做了一张问题速查表方便你在压测遇到问题时快速定位现象可能原因排查与解法连接数到X万左右就上不去压测机端口耗尽调大net.ipv4.ip_local_port_range或用多压测机连接一多就报“too many open files”文件句柄限制ulimit -n调大改/etc/security/limits.conf压测数据忽高忽低不稳定JVM参数/框架版本不一致统一镜像版本和JVM参数分别记录每次压测的配置压测时CPU跑不满QPS低瓶颈可能在压测工具排查压测机CPU占用使用更高性能的wrk/ghz配置内存持续增长疑似泄漏连接未正确关闭、内部缓存扩大用pprof或jmap做heap dump看对象引用链并逐步排查压测出的P99延迟比线上高很多单机压测核数不足增加压测机并发线程数或使用流量模型回放5.2 细节技巧压测时的监测与采样压测过程中我一直坚持一个习惯将压测机监控CPU、网络、内存与被测机监控独立记录。我使用Prometheus Grafana作为监控方案每5秒采样一次这样在出现问题时能快速定位是压测机瓶颈还是被测框架瓶颈。我还建议至少在测试过程中保留一份服务器端的火焰图或pprof采样。比如这次用Go框架在线压测的同时用go tool pprof抓取CPU profile就清楚地看到了一个热点是在JSON序列化/反序列化模块上。这个发现直接促使我们在业务代码改造时尽量用更高效的jsoniter来替代标准库encoding/json。如果你压的是Java框架也可以开启JFRJava Flight Recorder来做同样的分析。6. 高并发架构的后置思考选型只是开始压测数据指导我们完成了框架选型但它并不是整个高并发架构的终点。我们在完成选型后紧接着做了不止100个小的优化项这里主要挑几个容易忽略的点讲。6.1 agent框架与高并发业务的新结合搜索热词里频繁出现的agent框架比如LangChain、Dify、CrewAI和我们这个高并发消息中台也有交集。一个很现实的场景是IM消息的智能客服机器人需要调用大模型来生成回复这种Agent服务通常用Python实现但我们核心网关是Go。所以我们在网关层做了一个异步任务队列把需要Agent处理的消息投递到消息队列中由Python Agent服务去消费结果通过回调或WebSocket推回用户端。这种设计有个核心原则Agent处理绝对不能同步阻塞在网关的请求链路上。它带来两个明显好处一是网关的吞吐量不被AI推理的秒级耗时拖累二是Agent服务崩溃或重启时网关层完全无感消息会在消息队列中堆积等Agent恢复后再消费。6.2 长连接网关与业务服务的连接策略再分享一个真实场景里的优化。我们网关最终选择了Go但在某次压测中发现P99延迟比预期高了约10ms。排查后定位到是网关日志同步刷盘导致的IO阻塞。这里想提醒大家的是不要在一开始就把日志级别调成DEBUG高并发下每秒几十万条DEBUG日志会直接拖垮性能瓶颈。生产环境必须只保留WARN和ERROR级别日志并且日志输出要异步化。我赶工调整了日志写入方式使用异步logger并配置缓冲队列验证了这个问题得到解决。更有意思的是长连接客户端在弱网环境下频繁重连容易造成“惊群效应”压测时并没有体现但线上用户一多就暴露出来。这里就靠Go的gnet组件里自带的连接监听机制配合单核处理一定数量的连接手动做连接ID的Hash散列将重连请求均匀分发到各Worker协程上才算把它压住。最后的小建议个人经验技术选型的报告里千万不要只贴一张压测数据表就完事了。我通常会附上一段性能数据结论 成本估算 风险预案的总结性文字给后续的运维与维护同事参考。压测数据的意义不是告诉你哪个框架天下第一而是让你清晰知道每一项技术背后的代价。你选的不仅仅是框架更是整个团队未来一年要面对的可运维性与排障路径。总体来说如果在你的实际项目里也正面临高并发框架选择的纠结希望这篇记录能给你提供一个有参考价值的决策视角。压测数据是死的但你的技术考量是活的。如果你拿到的数据和我的不太一样也完全正常关键是你有没有用同样的控制变量去认真测过。