高并发系统设计:从理论到实践
凌晨5点,咖啡还没凉,我盯着屏幕上那行 panic: too many open files 的报错,心里一万只羊驼奔腾而过。上周五晚上,产品经理突然甩过来一个“小需求”:下周上线前,系统要支持每秒10万请求的压力测试——理由是“隔壁团队双11扛住了,我们也不能输”。
说实话,半年前我还是个坚定的“AI写代码反对者”。总觉得代码这东西,没亲手敲过、没被线上炸过、没在凌晨三点 debug 过,就不算真正掌握。但最近边刷 LeetCode 边准备跳槽,实在扛不住面试官灵魂拷问:“你做过高并发系统吗?怎么设计的?” 于是被迫从《分布式系统原理》啃起,一路摸爬滚打,居然真香了。
今天这篇,不讲大道理,就说说我怎么用 Go 搞定一个高并发下单系统,顺便聊聊为什么最后放弃了 Java + Spring Cloud 那套“全家桶”。
起因:不是我想卷,是业务逼的
我们团队负责的是一个电商秒杀模块。去年双11,系统直接崩了,DB 连接池打满,Redis 被打穿,运维大哥半夜打电话骂街。今年老板下了死命令:必须支持 10w QPS,延迟 < 50ms。
老架构是 Java 写的,Spring Boot + MySQL + Redis,单体部署。别说 10w,连 1w QPS 都稳不住。领导拍板重构,技术选型让我牵头(其实就是背锅)。
我第一反应是继续用 Java——毕竟团队熟。但一算账:要上微服务、熔断、限流、异步队列……光 Spring Cloud Alibaba 一堆组件就能把我绕晕。而且 JVM GC 停顿在高并发下太不可控,上次 Full GC 直接卡了 2 秒,用户都以为页面死了。
这时候,我翻了翻 GitHub 上几个高星项目,比如 go-kratos、go-zero,清一色 Go 写的,性能数据亮眼得离谱。再一看字节、B站、腾讯的开源项目,Go 几乎成了高并发场景的默认选项。
行吧,那就试试 Go。反正跳槽简历上也能写“主导高并发系统重构”,多香。
技术选型:为什么是 Go?
先说结论:Go 的轻量级 goroutine + CSP 并发模型,在 I/O 密集型场景下碾压线程模型。
| 维度 | Java (Spring Boot) | Go |
|---|---|---|
| 启动速度 | 几秒到十几秒 | < 100ms |
| 内存占用 | 单实例 > 500MB | 单实例 ~50MB |
| 并发模型 | 线程(1:1) | goroutine(M:N) |
| GC 停顿 | 可达数百 ms | 通常 < 1ms |
| 部署复杂度 | 需要 JVM、依赖管理复杂 | 静态编译,单二进制文件 |
最打动我的是 goroutine 的开销极低。一个 goroutine 初始栈只有 2KB,而 Java 线程默认 1MB。10w 并发,Java 得开 10w 线程?内存直接爆掉。Go 却能轻松 hold 住。
再加上 Go 的 channel 和 select,写异步逻辑简直丝滑。不用回调地狱,不用 CompletableFuture 嵌套三层,代码可读性拉满。
架构设计:三层防线,层层削峰
我们的目标很明确:不让任何一个请求直接打到数据库。于是搞了个三层防御体系:
- 接入层:Nginx + 限流
- 服务层:Go 微服务 + 本地缓存 + Redis 队列
- 数据层:MySQL 分库分表 + 异步落库
第一层:Nginx 限流兜底
先用 Nginx 做粗粒度限流,防住恶意刷单:
http {
limit_req_zone $binary_remote_addr zone=order_limit:10m rate=100r/s;
server {
location /create_order {
limit_req zone=order_limit burst=200 nodelay;
proxy_pass http://order-service;
}
}
}
别小看这一步,上线第一天就挡掉了 30% 的垃圾流量。
第二层:Go 服务层 —— 真正的战场
核心逻辑用 Go 重写。关键点有三个:
1. 前置校验 + 本地缓存
用户信息、商品库存这些高频读数据,先查本地缓存(用 bigcache),减少 Redis 压力:
// 本地缓存 10 秒,避免 Redis 成瓶颈
var userCache = bigcache.NewBigCache(bigcache.DefaultConfig(10 * time.Second))
func getUser(userID string) (*User, error) {
if data, err := userCache.Get(userID); err == nil {
var u User
json.Unmarshal(data, &u)
return &u, nil
}
// 回源查 DB 或 Redis...
}
2. Redis 队列削峰
下单请求不直接处理,而是推入 Redis Stream(比 List 更可靠):
func CreateOrder(ctx context.Context, req *OrderReq) error {
// 校验通过后,入队
_, err := redisClient.XAdd(ctx, &redis.XAddArgs{
Stream: "order_queue",
Values: map[string]interface{}{
"user_id": req.UserID,
"sku_id": req.SKU,
"ts": time.Now().Unix(),
},
}).Result()
if err != nil {
return fmt.Errorf("push to queue failed: %v", err)
}
return nil // 立刻返回,用户体验快
}
3. 消费者批量处理
后台起多个 goroutine 消费队列,批量写 DB:
func startConsumer() {
for i := 0; i < 10; i++ { // 10 个消费者
go func() {
for {
msgs, err := redisClient.XRead(ctx, &redis.XReadArgs{
Streams: []string{"order_queue", "0"},
Count: 100, // 一次最多取 100 条
Block: 1000 * time.Millisecond,
}).Result()
if err != nil || len(msgs) == 0 {
continue
}
batchProcess(msgs) // 批量插入 DB
// ACK 消息(简化版)
}
}()
}
}
这样,前端 10w QPS,后端可能只需要 1k TPS 就能消化完。
数据库:别让 MySQL 成短板
即使做了队列削峰,DB 仍是瓶颈。我们做了两件事:
- 分库分表:按 user_id hash 分 16 库,每库 64 表。
- 异步 Binlog 同步:用 Canal + Kafka 把订单数据同步到 ES,供查询用。
接口设计也讲究:写接口只返成功/失败,不返完整订单。查询走单独的 read service,彻底读写分离。
上线那天,我手抖了
压力测试跑起来那一刻,我心跳加速。10w QPS 打进来,监控面板上 CPU 稳在 60%,内存 200MB,P99 延迟 38ms。
运维小哥探头问:“这就完了?没报警?”
我说:“对啊,Go 程序,稳定得很。”
他一脸不信:“Java 那会儿,光 GC 日志就能占满一个屏幕。”
总结:真香警告
这次重构让我彻底扭转了对 Go 的偏见。它不是玩具语言,而是为云原生和高并发而生的利器。
如果你也在准备跳槽,或者被高并发需求折磨,我强烈建议你:
- 别死磕 Java 全家桶,Go 的生态已经足够成熟;
- 善用 GitHub,很多轮子(如 go-zero)直接集成限流、熔断、链路追踪;
- 高并发的核心不是技术多牛,而是“削峰填谷”的思路——让系统忙而不乱。
最后吐槽一句:产品经理上周又提新需求了,说要加“AI 推荐下单”。我笑了笑,心想:这次,或许真该让 AI 帮我写点代码了。
(完)
附:学习资源推荐
- GitHub 项目:go-zero(自带高并发最佳实践)
- 书籍:《Go 语言高并发与微服务实战》
- 工具:wrk(压测)、pprof(性能分析)
别卷了,早点下班。毕竟,代码跑得再快,也快不过 deadline。

评论 0