技术文章

♀朱丽
2026-06-30 15:33
阅读 713

聊聊我在杭州做高并发系统设计的一些实战思考

上周五晚上十一点半,滨江的夜风已经有点凉了。我关掉电脑上的浏览器,切回终端,看着 Neovim 里刚重构完的代码,长长地舒了一口气。作为一个在二线厂摸爬滚打了3年的前端,平时习惯了用 Vim 这种“上古神器”写写 Vue 和 React,讲究个代码洁癖和键盘流。但最近这大半年,我的代码人生算是彻底拐了个弯——为了年底能跳槽去阿里或者网易混个更好的坑位,也为了扛住公司今年那个坑爹的大促项目,我硬生生把自己逼成了一个半吊子全栈,开始死磕高并发系统设计。

讲真,前端搞高并发,听起来有点像让厨子去修发动机。但在现在的微服务架构下,前端早就不是只写写页面那么简单了。BFF(Backend For Frontend)层、网关层、甚至一些边缘计算节点,全都是高并发的重灾区。今天就来跟大家聊聊,我这几个月在实战中摸爬滚打,总结出的一些高并发系统设计经验。

那个差点让我背锅的大促项目

事情还得从今年年初说起。公司接了个挺大的电商业务,老板画了个大饼,说这次大促流量会是去年的三倍。当时产品经理天天拉着我们开会,需求加了一个又一个。而我们原来的 BFF 层是用 Node.js (Express) 写的,平时处理点普通的 CRUD 还行,一遇到这种高并发场景,直接就拉胯了。

去年双11期间,我们就吃过一次大亏。当时 QPS 刚破两千,BFF 层的接口响应时间(RT)直接从 50ms 飙到了 2000ms 以上,紧接着就是大面积的 502 Bad Gateway。运维大哥在钉钉群里疯狂 @ 我,测试那边也提了一堆接口超时的 Bug。我当时看着满屏的报错日志,真的想砸电脑。

痛定思痛,今年大促前,Leader 把 BFF 层重构和网关优化的任务交给了我。要求很简单:抗住预估的 10000 QPS,RT 控制在 100ms 以内。为了这个目标,我开始了漫长的技术选型和架构设计之路。

BFF 层技术选型大比拼

既然要重构,首先面临的就是技术选型。我们团队主要是前端,Node.js 是首选,但考虑到高并发和未来的扩展性,我也把 Go 和 Python 拉进了对比池。作为一个注重代码可读性和可维护性的 Vim 党,我对代码的“颜值”和逻辑的清晰度是有执念的,太反人类的语法我实在受不了。

下面是我做的一份详细的技术选型对比表:

维度 Node.js (NestJS) Go (Gin/Fiber) Python (FastAPI)
并发模型 单线程事件循环 + 异步非阻塞 协程 (Goroutine) + 多路复用 asyncio 协程 + 多进程/多线程
CPU 密集型表现 较差,容易阻塞事件循环 极佳,原生多线程/多进程支持 较差,受限于 GIL(全局解释器锁)
IO 密集型表现 优秀 极佳 优秀(需严格使用异步库)
开发效率 高(前端无缝切换) 中等(编译、类型检查) 极高(语法简洁,类型提示友好)
代码可读性 中等(回调/异步容易写出面条代码) 较高(语法严谨) 极高(Pythonic,强迫症福音)
生态与中间件 极其丰富 丰富,但偏向底层 丰富,尤其在数据处理和AI领域

经过几轮激烈的内部讨论(其实是我和后端大佬在会议室吵了三天),我们最终决定在核心的高并发网关层使用 Go,而在一些需要快速迭代、逻辑复杂的 BFF 聚合层,尝试引入 Python 的 FastAPI 框架。

为什么选 Python?你可能会问,Python 不是有 GIL 吗?高并发能行?其实,BFF 层绝大多数场景是 IO 密集型(调用下游微服务、查 Redis、查数据库)。在 FastAPI 的异步支持下,只要保证所有 IO 操作都是非阻塞的,Python 的协程性能完全能够胜任。更重要的是,Python 的代码可读性简直无敌,配合 Pydantic 做数据校验,代码写起来就像诗一样优雅,完美契合我的代码洁癖。

高并发架构设计与数据库考量

技术栈选好了,接下来就是真刀真枪的架构设计。高并发系统绝对不是单纯地堆机器,而是要在各个环节做“防御性设计”。

1. 缓存策略与 Redis 优化 缓存是高并发的救命稻草。我们在 BFF 层引入了多级缓存:本地缓存(Caffeine/Node-cache) + 分布式缓存(Redis)。 在设计 Redis 时,我特别关注了“热 Key”问题。大促时,某个爆款商品的详情接口 QPS 高达 5000,如果全打到 Redis 单个节点上,节点必挂。我们的解决方案是:在 BFF 层加本地缓存,设置极短的过期时间(比如 2 秒),同时在 Redis 端对热 Key 进行打散(加随机后缀),配合后端的多节点读取。

2. 接口设计与限流降级 接口设计必须遵循“快速失败”原则。我们在网关层引入了 Sentinel 做限流。对于非核心接口,直接配置了降级策略,一旦 RT 超过阈值,直接返回默认兜底数据,绝不拖垮主链路。 这里有个细节:我们在 Python BFF 层调用下游服务时,全部使用了 httpx 的异步客户端,并且严格设置了 timeout。千万别小看这个 timeout,不设超时,下游一挂,你的连接池瞬间就会被耗尽。

3. 数据库设计考量 虽然 BFF 层不直接写库,但我们会透传查询。对于复杂的聚合查询,我们坚决反对在 BFF 层做多次 DB 查询。我们推动后端把数据推到了 Elasticsearch,BFF 层只做简单的 ES 查询和结果组装。同时,对于写操作,全部通过消息队列(RocketMQ)进行异步削峰,BFF 层只负责发送 MQ 消息,直接返回“处理中”,彻底解耦。

线上踩坑与运维血泪史

理论总是完美的,但现实往往会给你一记响亮的耳光。在压测和实际上线的过程中,我踩了几个极其恶心的坑。

坑一:Python 异步代码里的“同步刺客” 这是让我最崩溃的一个 Bug。压测时,Python BFF 层的 QPS 死活上不去,CPU 占用率也不高,但 RT 极高。我一开始以为是 GIL 的问题,后来用 py-spy 一分析,发现事件循环(Event Loop)被卡死了。 顺藤摸瓜查了半天,发现团队里有个哥们,在异步接口里直接调用了一个第三方的同步 SDK 去请求外部接口!这个同步调用直接阻塞了整个主线程的事件循环,导致其他所有的异步协程都在排队等它执行完。 当时我真的想顺着网线过去掐死他。最后怎么解决的?我被迫引入了 run_in_executor,把那个同步 SDK 扔到了线程池里去执行,才算把这个问题平息。这也给我敲响了警钟:在异步框架里,永远不要信任队友的代码,Code Review 必须严格检查任何可能的阻塞调用。

坑二:连接池泄漏 在 Node.js 网关层,我们发现长时间运行后,偶尔会出现 socket hang up 的报错。排查了一圈,发现是 HTTP Agent 的连接池配置问题。默认的 maxSockets 太小,且没有正确处理 keepAlive。后来我们显式配置了 http.Agent,开启了 keepAlive,并合理设置了 maxFreeSocketstimeout,问题才彻底解决。

运维与监控 作为半个全栈,我也承担了一部分运维工作。高并发系统,没有监控就等于裸奔。我们搭建了 Prometheus + Grafana 的监控体系,不仅监控机器的 CPU、内存,还通过自定义 Exporter 监控 BFF 层的接口 QPS、RT、错误率,甚至细化到 Redis 的命中率和 MQ 的积压量。 我还写了几个 Python 脚本,配合 Alertmanager 做告警收敛。不然一旦出问题,钉钉群里的告警消息能把你手机卡死。现在看着 Grafana 上平稳的曲线,心里总算踏实了点。

总结与心得

经过这几个月的折腾,大促终于平稳落地。最终数据显示,BFF 层的核心接口 QPS 峰值达到了 12000,P99 RT 稳定在 80ms 左右,完美达成了目标。

回顾这段经历,我对高并发系统设计有了更深的体会。高并发不是玄学,它本质上是对系统资源的极致压榨和合理分配。从理论到实践,中间隔着无数个踩坑的日日夜夜。

  1. 敬畏异步:无论是 Node.js 还是 Python,异步编程能带来高并发,但也带来了状态管理和调试的噩梦。保持代码的纯粹,避免同步阻塞,是异步开发的第一法则。
  2. 防御性设计:永远不要相信下游服务是稳定的。限流、熔断、降级、超时,这些不是可选项,而是必选项。
  3. 可观测性:出了问题能快速定位,比不出问题更重要。完善的日志和监控体系,是程序员的救命稻草。

当然,这段经历也让我的技术视野开阔了不少。从只关注前端渲染,到理解整个链路的流转,这种全局观的提升,对我接下来的职业发展大有裨益。

不说了,Leader 刚在群里发话,下周要开始准备明年的架构演进方案了。我的代码人生还在继续,希望年底能顺利拿到阿里或网易的 Offer,去更大的平台看看。如果你也在研究高并发,或者对 Vim 有什么独门配置,欢迎在评论区交流。咱们江湖再见!

评论 0

最热最新
暂无评论
♀朱丽Lv.1
0
影响力
0
文章
0
粉丝