高并发不是玄学,是我被逼出来的实战经验
上周五晚上十一点半,我正戴着耳机听着Lo-fi beats敲Go代码,突然手机疯狂震动——线上支付服务又崩了。产品经理在群里发了个“老板问什么时候能修好”,后面还跟了个狗头表情。那一刻我真想把键盘砸了,但转念一想:这破系统是我接的外包项目,修不好不仅拿不到尾款,下周租房都成问题。
坐标北京,每天通勤一小时,副业搞外包已经快两年了。白天在大厂写CRUD,晚上回家还得给甲方调接口。说白了,高并发这玩意儿,不是我在研究它,是它天天追着我打。
事情得从三个月前说起。一个做电商SaaS的朋友找我,说他们准备冲今年618,预估QPS要干到5000+。我一听就头皮发麻——他们现在的架构还是单体MySQL+PHP,连缓存都没上全。但架不住对方报价实在诱人,加上我也想给自己简历添点硬核项目,咬咬牙接了。
别被GPT-4忽悠了,高并发得自己动手
说实话,一开始我也想偷懒。打开GPT-4,输入:“如何设计高并发电商系统?用Go语言”。AI唰唰给我列了一堆方案:微服务、消息队列、分布式缓存、读写分离……看起来挺像那么回事。但当我真去落地时才发现,很多建议根本不接地气。
比如GPT-4建议我直接上Kafka做订单削峰。可问题是,甲方团队总共就三个后端,连Docker都还没玩明白,你让我搞Kafka集群?运维成本直接爆炸。后来我改成用Redis Streams,轻量又够用,这才稳住。
所以我的结论是:GPT-4可以当思路启发器,但不能当架构师。真正的高并发设计,得结合业务规模、团队能力和上线deadline来权衡。
从单点崩溃到扛住5000 QPS,我是怎么改的
第一步:别急着分库分表,先看看瓶颈在哪
很多人一提高并发就想到分库分表,其实这是典型误区。我先用pprof对现有接口做了性能分析,发现90%的时间花在两个地方:
- 商品详情页每次都要查库存、促销、评价,串行请求拖慢响应
- 订单创建时直接写MySQL,高峰期锁表严重
于是第一轮优化根本没动架构,而是:
- 把商品详情的多个查询并行化(Go的goroutine真是香)
- 订单创建走异步队列,前端先返回“处理中”,后台慢慢落库
// 简化版的商品详情聚合
func GetProductDetail(id int64) (*Product, error) {
var (
product *Product
stock int
promo *Promotion
err error
)
// 并行拉取数据
eg := errgroup.Group{}
eg.Go(func() error {
product, err = productRepo.Get(id)
return err
})
eg.Go(func() error {
stock, err = inventoryService.GetStock(id)
return err
})
eg.Go(func() error {
promo, err = promoService.GetCurrent(id)
return err
})
if err := eg.Wait(); err != nil {
return nil, err
}
product.Stock = stock
product.Promo = promo
return product, nil
}
这一招下去,接口P99从800ms降到150ms,QPS直接翻倍。很多时候,优化比重构更有效。
第二步:缓存策略不能只靠直觉
缓存确实是高并发的银弹,但怎么用很讲究。我见过太多团队把Redis当成万能垃圾桶,结果缓存击穿、雪崩、穿透三连击。
这次我定了三条铁律:
- 热点数据永不过期:比如首页Banner,用后台定时刷新代替TTL
- 缓存空值防穿透:查不到的商品ID也缓存5秒,避免DB被打爆
- 本地缓存兜底:用Go的
sync.Map做一级缓存,减少Redis压力
特别说下本地缓存。很多人觉得没必要,但在北京这种网络抖动频繁的地方(尤其晚高峰地铁上写代码时深有体会),少一次Redis往返能省5-10ms。对于5000 QPS的系统,这就是50秒 vs 100秒的区别。
第三步:数据库设计要为高并发让路
最开始订单表是这么设计的:
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
status TINYINT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- ...其他字段
);
问题在哪?user_id没索引!每次查用户订单都要全表扫描。更致命的是,所有状态变更都在这张表里update,行锁竞争激烈。
后来拆成两张表:
orders只存核心字段,插入后基本不再修改order_status_logs记录状态变更流水
同时给user_id + created_at建联合索引。查询用户最近订单时,直接走覆盖索引,速度飞起。
| 优化项 | 优化前 QPS | 优化后 QPS | 延迟下降 |
|---|---|---|---|
| 接口并行化 | 1200 | 2500 | 82% |
| Redis缓存策略 | 2500 | 3800 | 45% |
| DB读写分离 | 3800 | 5200 | 30% |
(注:测试环境4核8G,MySQL 8.0,Redis 6.2)
血泪教训:别在周五下午改生产配置
说了这么多技术,其实最大的坑往往出在流程上。
有一次我信心满满地上了读写分离,结果周一早上收到报警——从库延迟飙到30分钟。排查半天才发现,主库开了sync_binlog=1,但从库为了性能关了sync_relay_log,遇到突发流量relay log堆积如山。
还有一次更惨。为了压测极限性能,我把连接池调到500。结果没注意云厂商的VPC带宽限制,把整个可用区的网络打满了,连SSH都连不上。运维大哥半夜打电话骂我:“你小子是不是想让我失业?”
这些经历让我明白:高并发系统不仅是技术问题,更是工程问题。现在我每次上线都坚持三件事:
- 先在影子库跑一周流量回放
- 关键配置变更必须配熔断开关
- 绝不在周五下午四点后动生产环境(血的教训!)
写在最后:高并发没有银弹,只有权衡
搞完这个项目,我最大的感悟是:所谓高并发架构,本质是在成本、复杂度和性能之间找平衡点。
甲方不是阿里腾讯,不需要支撑百万QPS。对他们来说,一个稳定、可维护、能扛住大促的系统,远比炫技的微服务架构重要。所以我最终没上K8s,没搞Service Mesh,甚至连消息队列都只用了Redis Streams——但系统稳稳撑过了618,峰值QPS 5800,错误率低于0.1%。
昨晚结款到账,我请自己吃了顿海底捞(好吧其实是外卖)。边吃边想:下次接外包,是不是该把“高并发经验”写进报价单里?
对了,如果你也在北京搞副业,欢迎交流。不过别在晚高峰地铁上找我——信号太差,我连Git都pull不下来。

评论 0