高并发系统设计:一个老程序员的实战经验分享
开始前的一句话

去年我参与了一个电商促销平台的重构项目,当时我们团队面对的最大挑战是:“如何在秒杀场景下支撑每分钟上百万次请求而不崩?”
如果你也碰到过类似的问题,那你一定知道这种压力不是一般的架构能扛得住的。这篇文章我会以第一人称的方式,讲讲我在那个项目中遇到的实际问题、做出的选型决策、踩过的坑以及学到的教训。
这不是一篇纯理论的文章,而是一个真实项目的“术后复盘”。希望你读完之后,下次再遇到高并发的场景时,心里能更有底气。
一、项目背景与技术挑战

这个项目是一个全国性的线上促销活动平台,每年有几次大促,比如“双11”、“618”等,每次活动都会带来巨大的流量。之前系统的架构已经不堪重负:数据库崩溃、接口超时、页面打不开几乎是每次大促期间的标配。
我们的目标很明确:
- 要能支持每秒几千到上万次请求
- 活动期间要保证核心业务(比如下单)可用
- 数据一致性不能出问题,不能出现超卖
- 系统故障后要有快速恢复机制
一开始我们以为只要加几台服务器、上点缓存就行了。但真正深入进去才发现,事情没那么简单。
二、第一次尝试:从单体架构出发
最初的设计是个典型的Spring Boot单体应用,配合MySQL和Redis做缓存。前端直接调用接口,所有逻辑都集中在一个服务里,连库存管理都在业务代码里硬编码处理。
结果可想而知:
- 秒杀开始3分钟后,订单服务就开始频繁出现Timeout
- MySQL主库连接数暴涨,很多查询被阻塞
- 接口响应时间飙升到5s以上
- 更可怕的是,库存出现了超卖!
这个问题直接导致公司损失了几万块钱的补贴资金,也让我们意识到:原来并发不只是压测的时候跑得动就万事大吉了。
三、架构改造之旅
1. 拆分服务 + 异步化
首先,我们把原来的单体服务拆成了几个微服务:
- 用户服务(认证、信息)
- 商品服务(商品详情、库存查询)
- 订单服务(创建订单、库存扣减)
- 支付服务(支付回调、状态更新)
但这还不够,因为订单创建和库存扣减是耦合在一起的,用户一点击购买,就会同步去操作数据库,这在高并发下简直是灾难。
于是我们引入了 消息队列:使用Kafka进行异步解耦。当用户发起购买请求,先记录一个“待处理”的订单状态,并将任务发送给订单处理队列,后续由消费端逐步执行扣库存、生成正式订单等操作。
这样一来,前端接口可以做到“即时响应”,而后端异步处理真正的逻辑,大大缓解了数据库压力。
2. 缓存策略优化
我们之前只用了Redis缓存商品基本信息,但库存信息还是放在数据库里查。这就造成了一个瓶颈——热点数据集中在某些SKU上,MySQL读压力剧增。
我们做了两件事:
- 将库存单独存入Redis,每个SKU维护一个计数器
- 使用Lua脚本实现原子性扣减(避免超卖)
举个例子,当一个用户来下单某个商品,我们通过Redis Lua脚本来判断当前库存是否足够:
-- 扣库存脚本
local key = KEYS[1]
local num = tonumber(ARGV[1])
local stock = redis.call("GET", key)
if not stock or stock < num then
return -1
end
redis.call("DECRBY", key, num)
return 0
这样整个扣库存的过程在Redis内部完成,既快又安全。
当然,Redis也不能百分百可靠,所以我们同时在数据库中也做了对账逻辑,每天凌晨定时校验库存差异并修正。
3. 数据库优化
数据库方面我们主要做了以下几点:
- 读写分离:使用MySQL主从复制,写走主库,读走从库
- 分库分表:对订单表按用户ID做水平拆分,分散负载
- 索引优化:针对查询条件建立联合索引,避免全表扫描
- 冷热分离:历史订单迁移到归档库,减少在线表的数据量
还有一个很重要的点是:事务控制要合理。以前有些接口用了@Transactional注解,但在高并发下会导致数据库锁竞争激烈。后来我们把部分操作改为手动提交事务,甚至完全拆成非事务性操作,用幂等性和补偿机制来保证最终一致性。
四、部署和监控体系建设
1. 容器化 + 自动扩缩容
项目上线前我们完成了容器化改造,基于Docker+Kubernetes部署,借助Helm统一管理配置和部署流程。
在Kubernetes中,我们设置自动伸缩规则(HPA),根据CPU或请求延迟动态调整Pod数量。例如:
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: order-service
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: order-service
minReplicas: 4
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
这套机制让我们在流量高峰时能够迅速扩容,低谷时释放资源,节省了不少成本。
2. 监控和告警体系
我们搭建了Prometheus+Grafana来做实时监控,Zabbix用于节点级监控,再结合AlertManager做告警推送。
监控指标包括:
- 请求成功率、P99耗时、QPS等接口性能指标
- Redis内存、命中率
- MySQL慢查询、连接数
- Kafka堆积情况
- 系统CPU、网络IO、磁盘使用情况等
一旦某项指标超过阈值(比如订单服务QPS > 10000),就会自动触发钉钉/飞书告警,提醒值班人员介入处理。
五、生产环境的那些小插曲
说再多理论都不如几个实际案例更让人印象深刻。
案例一:Redis雪崩
有一次凌晨三点,我们在做压测准备的时候,突然发现首页加载异常缓慢。排查发现,大量商品缓存几乎在同一时刻失效,导致所有请求都打到了数据库,数据库瞬间被打满,CPU飙到99%。
我们紧急做了两件事情:
- 给缓存加上随机过期时间(TTL基础上加一个随机偏移)
- 启动本地缓存作为降级兜底,防止全部穿透
这次事件后,我们增加了缓存预热机制,在大促开始前主动刷新热点数据。
案例二:Kafka堆积
活动期间订单消费端一度积压了几百万条消息。原因是其中一个节点因为JVM Full GC长时间没有响应,导致拉取进度落后。
我们后来做了几点优化:
- 增加消费线程数量
- 对Consumer组做细粒度分区
- 设置合理的Offset提交策略,避免重复消费
- 加上消费进度看板,及时发现滞后情况
这些措施让整体消费速度提升了一倍多。
六、效果总结
经过半年的迭代优化,我们最终实现了以下几个关键目标:
| 指标 | 改造前 | 改造后 |
|---|---|---|
| QPS | 最高约2000 | 达到10000+ |
| 订单创建平均耗时 | 800ms | 300ms以内 |
| 平均TP99延迟 | 3s+ | 500ms内 |
| 故障恢复时间 | 1小时左右 | 5分钟以内 |
| 系统可用性 | 约99% | 稳定在99.99%以上 |
最重要的是,我们成功撑过了当年“618”大促,全程无重大事故,也没有超卖和漏单。
七、我的一些经验和建议
如果你正在或者即将做类似的项目,以下是我踩过的坑和总结的经验:
1. 架构不要一开始就过度设计,但也别图省事
微服务不是银弹,但适当的模块划分非常重要。早期我们图省事一直用单体结构,结果后面拆起来痛苦不堪。
建议:
- 初期可以先模块化,后面拆微服务更容易
- 不要过度追求“高可用”,先保证“可用”
- 技术方案要留退路,比如预留降级开关
2. 缓存要用好,但也要小心掉坑
缓存是一把双刃剑。合理使用可以提升性能,但如果不注意缓存击穿、雪崩、穿透等问题,反而会拖垮整个系统。
建议:
- 缓存过期时间加上随机扰动
- 关键数据做好缓存预热
- 使用本地缓存作为第二层兜底
3. 高并发场景下,异步才是王道
同步操作往往成为瓶颈,特别是在需要操作数据库的地方。引入MQ、异步队列、延迟任务等方式,能有效减轻系统压力。
建议:
- 复杂操作尽量异步化
- 考虑引入分布式事务或Saga模式来保障一致性
- 使用幂等机制防止重复处理
4. 监控必须尽早接入,别等出了事才想起补
很多团队前期只顾功能开发,监控是最后几天临时加的。结果上线之后各种问题都看不见,只能靠日志硬刚。
建议:
- 监控系统要尽早集成
- 每个服务都暴露/metrics接口
- 设置预警规则,提前预警
写在最后:关于技术成长的一些思考
这一年做下来,我最大的感悟是:
“真正的高并发,不是压测跑得动,而是在线上扛得住。”
我们做的每一个架构改动、每一次代码优化,都不是为了看起来有多酷炫,而是为了让系统在压力下依旧能稳定运行。
做技术的人有时候容易陷入“工具主义”的误区,总想着用最新的框架、最火的架构来解决问题。其实,真正的高手是在纷繁复杂的限制条件下,找到性价比最高的平衡点。
愿你在未来的项目中也能少走弯路,写出“稳如老狗”的系统。
如果你也在做高并发相关的工作,欢迎留言交流,我们可以一起讨论具体的技术细节或者踩过的坑 😊

评论 0