高并发系统设计:从理论到实践 —— 一位架构师的实战思考
引言:为什么我要写这篇文章?

去年在一家中大型电商公司负责一个秒杀活动系统的重构工作。那次项目是我职业生涯中第一次真正意义上面对“高并发”这个词汇,也彻底改变了我对后端系统设计和性能优化的认知。
在那次任务中,我们面临的挑战是:如何支撑单次促销活动中每秒十万级别请求涌入的场景?系统不能崩、订单不能超卖、库存不能出错,用户感知要流畅——这不仅仅是技术问题,更是整个系统的工程化能力考验。
我想通过这篇文章,分享我们在那段时间遇到的问题、采用的技术方案以及一些真实的踩坑经历。这篇文章不会空谈理论,也不会堆砌术语,而是结合真实项目背景,讲述我是如何一步一步把一个可能崩溃的系统优化成稳定运行的高并发服务的。
问题描述:一场被击垮的秒杀系统

事情发生在一次大促活动前的压力测试阶段。我们的原始秒杀系统基于Spring Boot + MySQL + Redis 架构,前端通过Nginx负载均衡到多个节点。按照以往的经验,这种结构应对上万QPS应该不成问题,但事实却狠狠打脸。
当压测工具发出10k TPS时,系统就开始频繁出现如下现象:
- 数据库连接池被打爆
- Redis大量缓存击穿导致雪崩
- 接口响应时间从200ms飙升至数秒甚至超时
- 订单重复生成
而最严重的情况发生在真实流量模拟阶段:某个热门商品的秒杀链接刚一放出,不到30秒,系统直接挂掉,应用服务器CPU飙到99%,MySQL主库死锁频发,Redis内存占满,业务流程中断。
那一刻我们意识到:现有的架构根本无法承载真正意义上的“高并发”。
解决思路:分层拆解,逐个击破

我们决定对系统进行重构,目标明确:支持10W QPS的瞬时并发访问,并确保数据一致性。
为了实现这个目标,我带领团队从四个维度出发进行设计与优化:
- 限流与熔断机制(防御性策略)
- 异步处理与削峰填谷(系统缓冲)
- 数据库与缓存协同优化(数据层面保障)
- 分布式扩展与服务治理(横向扩容)
接下来我会逐一展开说明当时的思路和具体实现过程。
第一步:限流 & 熔断,构建第一道防线
使用Sentinel进行限流控制
我们引入了阿里开源的 Sentinel 来作为统一的限流组件。
在核心入口处,比如/seckill接口添加资源保护规则:
// 在接口初始化时注册资源并配置规则
initFlowRules();
public void initFlowRules() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("seckill");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 每秒最多允许处理5000个请求
rule.setCount(5000);
rule.setLimitApp("default");
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
@GetMapping("/seckill")
@SentinelResource(value = "seckill", blockHandler = "handleSeckillBlock")
public Result handleSeckill() {
// 正常逻辑处理
}
public Result handleSeckillBlock(BlockException e) {
return Result.fail("当前人数太多,请稍后再试");
}
这个措施大大降低了系统崩溃的可能性。即使有突发流量,也能将风险限制在可控范围内。
熔断降级策略
使用Hystrix(虽然后来已不维护,但我们当时用的还是它)做了熔断策略:
@HystrixCommand(fallbackMethod = "fallbackSeckill")
public Result doSeckill() {
// 调用其他服务或关键链路
}
private Result fallbackSeckill(Throwable t) {
log.warn("触发熔断:{}", t.getMessage());
return Result.fail("系统繁忙,请稍后再试");
}
虽然现在更推荐Resilience4j或者Sentinel内置熔断功能,但在生产环境中我们验证了这种方式确实能有效防止雪崩效应。
第二步:异步处理,降低同步压力
将下单逻辑异步化
最初的秒杀流程是这样的:
- 校验登录
- 判断库存是否足够
- 创建订单
- 扣减库存
- 返回结果
这套同步流程在低并发下没问题,但一旦并发上来,数据库事务、锁竞争都成了瓶颈。
我们重构了这部分逻辑:
- 用户点击“购买”,立即返回抢购排队页面;
- 实际下单操作交由消息队列处理(Kafka);
- 后台服务消费消息,完成实际的订单创建与库存扣减;
- 最终通过WebSocket通知用户成功或失败。
关键改动之一是使用 RocketMQ 做队列投递(也可以用 Kafka):
/**
* 秒杀下单入口
*/
@PostMapping("/start")
public Result startOrder(@RequestBody SeckillRequest request) {
boolean result = seckillService.handlePreCheck(request);
if (!result) {
return Result.fail("抱歉,库存不足");
}
// 放入消息队列,异步处理
rabbitTemplate.convertAndSend("seckill.order.queue", request);
return Result.success("提交成功,请稍候查看结果");
}
这样既保证了用户体验的流畅性,又有效缓解了数据库的压力。
延迟双删,避免缓存不一致
库存相关数据我们采用了“延迟双删”的方式保证最终一致性:
@Override
public void deleteStockFromCache(int productId, int count) {
// 先删除缓存
redisUtil.del("stock:" + productId);
// 执行数据库减库存
seckillDao.decreaseStock(productId, count);
// 延迟一段时间再删一次缓存,防止旧数据残留
Executors.newScheduledThreadPool(1).schedule(() -> {
redisUtil.del("stock:" + productId);
}, 500, TimeUnit.MILLISECONDS);
}
这是一种比较折中的做法,在实际运行中效果很好,尤其适合缓存更新频率较高的场景。
第三步:数据库与缓存优化
缓存前置,预加载+热点Key发现
为避免缓存穿透和击穿,我们做了以下几个改进:
- 提前在活动开始前将参与秒杀的商品库存信息预加载进Redis;
- 使用本地缓存+Caffeine缓存热点商品基本信息;
- 监控Redis慢查询日志,识别热点Key。
# Redis 缓存配置示例
spring:
cache:
caffeine:
spec: maximumSize=500,expireAfterWrite=5m
此外,我们在网关层加了一层缓存代理,过滤无效请求:
location /seckill {
proxy_pass http://backend;
# 对特定url做缓存,提升吞吐
proxy_cache seckill_cache;
proxy_cache_valid 200 302 60s;
proxy_cache_valid 404 1m;
proxy_cache_bypass $arg_nocache;
}
数据库拆分与读写分离
我们将原来的单一MySQL数据库进行了垂直拆分:
- 订单库、库存库、用户库物理隔离;
- 库存表单独建一张高性能的TINYDB存储;
- 主从复制实现读写分离;
- 写操作走主库,读操作走从库;
- 索引优化、SQL改写、减少全表扫描。
另外,为了防止数据库事务阻塞,我们在库存变更的关键路径上使用了CAS(Compare And Set)乐观锁模式:
UPDATE inventory SET stock = stock - 1 WHERE product_id = ? AND stock > 0;
只有在条件成立的情况下才会执行扣减,避免超卖问题。
第四步:分布式架构升级与服务治理
微服务拆分+容器编排
原有的系统是一个单体结构,难以水平扩展。我们将其拆分为几个核心微服务:
- 商品服务
- 库存服务
- 下单服务
- 活动调度服务
每个服务独立部署,依赖于 Spring Cloud Alibaba + Nacos 进行服务发现和配置管理。
我们采用 Kubernetes 容器化部署,根据流量动态伸缩Pod数量:
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: seckill-service-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: seckill-service
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 80

该方式让我们可以根据实时负载自动扩容,极大提升了系统的可用性和弹性。
分布式锁的应用
由于库存的变动涉及多实例之间的一致性,我们引入了 Redlock 和 Redisson 分布式锁机制:
RLock lock = redisson.getLock("stock_lock_" + productId);
boolean isLocked = false;
try {
isLocked = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (isLocked) {
// 执行扣减库存逻辑
}
} finally {
if (isLocked) {
lock.unlock();
}
}
当然,在高并发下获取锁也可能成为瓶颈,因此我们做了锁粒度控制,只在关键路径加锁,非关键路径放宽约束。
踩过的坑与解决方法
在开发与上线过程中,我们也遇到了不少坑:
坑一:Redis雪崩和缓存失效风暴
一开始没有设置随机过期时间,结果很多缓存在同一时刻失效,全部落到数据库,瞬间拖垮服务。
解决方法:
给缓存设置一定的随机过期窗口,例如:
redisUtil.set("stock:1001", stock, 10 + random.nextInt(5), TimeUnit.MINUTES);
坑二:消息积压处理不当
最初用 RabbitMQ 处理异步下单,但由于下游服务处理能力跟不上,消息越积越多,一度导致磁盘爆满。
解决方案:
- 增加消费者线程;
- 设置最大堆积阈值自动扩容;
- 关键业务使用优先级队列。
坑三:数据库事务长耗时导致死锁
事务未及时释放导致死锁,尤其是在并发插入订单表时尤为明显。
修复办法:
- 尽量缩短事务周期;
- 减少锁粒度;
- 加字段索引、优化SQL语句。
效果总结:从崩溃边缘走向稳定运行
经过三个月的重构与优化,最终系统在双十一当天的表现令人满意:
- 平均 QPS 达到 7W+,峰值超过 10W
- 系统稳定性大幅提升,无重大故障发生
- 用户转化率比往年提高 20%
- 事后统计发现库存错误率为 0,订单准确性 100%
更难得的是,这次改造不仅解决了高并发问题,还为我们后续的微服务架构演进奠定了良好基础。
经验分享:写给正在搭建高并发系统的你
如果你现在正在设计或面临一个高并发系统,我可以分享几点宝贵的建议:
✅ 从小处入手,逐步迭代
不要一开始就把架构设计得过于复杂。先抓住最关键的瓶颈,如限流、缓存、数据库等,一步步拆解。
✅ 重视监控与报警
一套好的监控体系(Prometheus + Grafana + ELK),能让你在系统出问题时第一时间发现异常,而不是靠用户投诉才知道服务挂了。
✅ 技术不是万能的,工程化才是关键
再好的技术选型如果缺乏良好的团队协作、流程规范和文档沉淀,也会在实践中翻车。
✅ 不要迷信新技术,适合自己才最重要
比如我们现在已经在往云原生方向过渡,但当年的 RabbitMQ、Redis、Spring Boot 也都发挥了很大作用。技术选型要服务于业务目标。
结语:高并发不是终点,而是起点

回望那段高压下的重构之旅,我最大的感悟就是:“高并发”并不是某种技术名词,也不是某个框架特性,它是一套完整的系统工程思维。
它要求你理解业务模型、掌握技术细节、熟悉运维体系、还要有冷静判断问题的能力。
我希望这篇文章能帮你在面对高并发场景时少走点弯路,更重要的是,建立起自己的系统设计思维模型。
如果你也在从事类似的工作,欢迎留言交流,一起探讨高并发的设计之道。

评论 0