高并发系统设计:从理论到实践,一个后端开发者的实战分享
引言:为什么我决定写这篇文章?

作为一名在互联网公司工作的后端开发者,这几年来我参与过多个高并发系统的搭建和优化工作。从电商秒杀活动、社交平台的热点事件推送,到金融场景下的高频交易接口,每一次都让我对“高并发”这三个字有了更深的理解。
记得刚入行那会儿,我也听过不少关于高并发的设计方法论,比如缓存、队列、分布式、限流等等。但真正上手的时候才发现,纸上得来终觉浅。很多时候理论讲得很清楚,一到生产环境就发现这也不够用、那也出问题。
于是我就想着,不如结合自己的实际项目经验,写一篇真正贴地气的总结文章。希望通过这篇文字,不仅能帮大家更好地理解高并发系统的构建逻辑,也能少走一些我们踩过的坑。
项目背景:一场突如其来的“秒杀风波”

事情要回溯到2021年,当时我在一家中型电商平台负责订单中心的研发工作。临近618大促,我们需要上线一款限时秒杀商品,预计会有大量用户同时涌入下单页面。
系统情况简述:
- 主语言为Java(Spring Boot框架)
- 数据库使用MySQL分库分表,主从架构
- Redis用于缓存热点数据
- 系统部署在Kubernetes集群上
- 秒杀商品库存总量为5000个,售价极低,预计会在几秒钟内被抢空
为了提前预演风险,我们在测试环境中做了压测。初步结果还不错,TPS能达到2000左右,QPS也能扛住每秒万次访问量。当时的我想着,这不挺稳的吗?哪知道正式上线当天直接被打脸了……
遇到的问题与挑战:流量高峰下崩溃的系统

正式开始前五分钟,我们就陆续收到监控告警:Redis连接超时、MySQL锁等待严重、服务响应延迟飙升。等到秒杀开始那一刹那,订单服务直接全线不可用,前端一片惨白。
事后复盘发现,问题主要集中在以下几个方面:
1. 缓存穿透 + 击穿 + 雪崩全中
虽然我们给商品信息加了Redis缓存,但在高并发请求下,很多没有命中缓存的数据导致数据库压力剧增。同时,缓存失效时间设置不合理,导致大量缓存同一时间过期,引发了雪崩效应。
2. 库存扣减逻辑出现线程安全问题
原本我们使用的是先查询再更新的方式进行库存扣除,但在多线程并发下出现了超卖现象。虽然MySQL本身有行级锁机制,但由于应用层频繁发起事务,导致死锁频发、性能急剧下降。
3. 没有限流降级机制
当时的服务没有任何限流机制。当大量请求蜂拥而至时,服务根本来不及处理,全部堆积在业务逻辑里,最终引发链式反应——所有服务都被拖垮。
4. 没有异步化处理机制
订单创建、支付回调等流程没有引入消息队列,而是全部通过同步方式处理。在瞬时峰值到来时,整个链路都处于满负荷状态,几乎无暇顾及其他正常请求。
那次事故给我们敲响了警钟,也让整个技术团队意识到一个问题:我们缺的不是理论知识,而是真正的高并发落地能力。
解决方案:重构秒杀系统,实现“稳如老狗”的高并发设计
痛定思痛之后,我们决定对整个秒杀系统进行彻底重构。这个过程持续了一个半月,涉及代码重构、架构调整、压测验证等多个环节。
1. 缓存策略升级:三重防护
① 布隆过滤器防穿透
我们引入了一个本地布隆过滤器(使用Guava的Cache类实现),在请求进入DB之前进行拦截。这样能有效防止无效请求打到数据库。
LoadingCache<String, Boolean> bloomFilter = Caffeine.newBuilder()
.maximumSize(1000)
.build(key -> {
// 实际查询数据库确认是否存在该key
return existsInDatabase(key);
});
② 缓存永不过期或随机过期时间
我们将热点商品的缓存设为永不过期,并由后台定时更新。非热点商品则采用随机过期时间(比如基础TTL + 一个随机值),避免集中失效。
③ 多级缓存结构
除了Redis之外,我们还在应用节点使用了本地缓存(Caffeine)作为第一道防线,避免所有请求都经过Redis,降低网络延迟。
2. 使用Redis Lua脚本解决库存原子性问题
为了避免超卖问题,我们改用Redis+Lua来控制库存的原子扣减。具体做法是:把库存预热到Redis中,每次扣减时通过Lua脚本来保证操作的原子性。
示例Lua脚本如下:
-- KEYS[1] 是商品ID,ARGV[1] 是要减少的数量
local stock = redis.call('GET', KEYS[1])
if tonumber(stock) < tonumber(ARGV[1]) then
return -1
else
return redis.call('DECRBY', KEYS[1], ARGV[1])
end
Java代码调用:
public long deductStock(String productId, int quantity) {
Long result = redisTemplate.execute(
new DefaultRedisScript<>(deductLuaScript, Long.class),
Arrays.asList(productId),
String.valueOf(quantity)
);
return result;
}
这种方式大大减少了MySQL的压力,同时也解决了超卖问题。后续才异步持久化到MySQL中。
3. 接口限流 + 降级策略落地
我们采用了Guava的RateLimiter做客户端限流,以及Sentinel做服务端限流双保险。
客户端限流配置:
// 单个实例每秒最多处理1000个请求
RateLimiter rateLimiter = RateLimiter.create(1000);
if (!rateLimiter.tryAcquire()) {
throw new TooManyRequestsException("请求过于频繁,请稍后再试");
}
Sentinel规则配置(基于Nacos动态配置):
flow:
rules:
- resource: /api/secKill
limitApp: default
grade: 1
count: 1000
strategy: 0
controlBehavior: 0
clusterMode: false
同时我们还实现了简单的服务降级逻辑:当Redis异常或库存耗尽时,直接返回静态页面引导用户到其他商品页面,而不是一直抛异常。
4. 异步化改造 + 消息队列削峰填谷
我们将订单创建这一核心流程进行了异步化改造,借助Kafka将订单写入解耦出来:
@PostMapping("/secKill")
public ResponseEntity<?> handleSecKillRequest(@RequestBody SecKillRequest request) {
if (redisService.deductStock(request.getProductId(), request.getUserId())) {
// 扣减成功,发送消息到Kafka
orderProducer.sendOrderMessage(request.getUserId(), request.getProductId());
return ResponseEntity.ok("排队中,请勿重复提交");
} else {
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("库存不足");
}
}
然后订单消费者异步消费这条消息,完成真实落单动作:
@KafkaListener(topics = "order-topic")
public void processOrder(OrderMessage message) {
try {
orderService.createOrder(message.getUserId(), message.getProductId());
} catch (Exception e) {
log.error("订单处理失败", e);
}
}

这样一来,既避免了阻塞主线程,又实现了“削峰填谷”,让系统更稳定。
5. 数据库层面优化
虽然Redis已经挡掉了大部分请求,但我们还是对MySQL进行了一些针对性优化:
- 对订单表增加二级索引,加快用户订单查询
- 使用分库分表策略,每个用户的数据根据user_id取模分配到不同实例
- 写操作尽量使用Batch Insert,减少事务次数
- 加大连接池大小(HikariCP)
6. 整体架构图一览
Client ---(限流)--> API Gateway
|
Load Balancer
|
Nginx/Redis(前置缓存)
|
Spring Boot Application Cluster
|
Kafka(订单异步处理)
|
MySQL 分库分表 + 主从架构
整体架构以“缓存前置 + 异步解耦 + 限流保护”为核心思想,尽可能把压力分散到各个环节。
效果总结:系统终于稳住了!
这套新系统上线后,我们进行了多次压测,效果非常显著:
| 指标 | 原系统 | 新系统 |
|---|---|---|
| TPS | 2000+ | 2.5w+ |
| 平均响应时间 | 500ms | 120ms |
| CPU占用率 | 95%+ | 稳定在60%以下 |
| 成功订单数 | 存在超卖 | 全部正确 |
| 用户体验 | 页面卡顿频繁 | 流畅无卡顿 |
最让人安心的是,即使在模拟5倍流量冲击的情况下,系统也能自动限流并平稳过渡,不再出现服务宕机的情况。
那次618之后,老板还专门开了一个小会表扬我们:“你们这次做得不错,至少首页没挂。”
经验分享:高并发系统设计的关键点总结
1. 不要轻视任何一次压测
很多人认为压测只是形式主义,但我亲身经历过才知道:压测暴露的问题往往是你平时意识不到的盲区。建议一定要按真实场景模拟压测,不要只关注平均值。
2. “缓存 + 异步 + 限流”三板斧必不可少
这三个点几乎是所有高并发系统的基础标配。特别是异步化设计,它不仅提升性能,还能极大增强系统的健壮性。
3. 重视幂等性设计
在高并发环境下,网络抖动、重试机制很容易造成请求重复。建议在关键接口中加入幂等Token校验逻辑,避免重复处理。
例如,在订单创建接口中,可以传一个全局唯一ID作为去重标识:
String uniqueId = UUID.randomUUID().toString();
if (idempotentChecker.exists(uniqueId)) {
return "请勿重复提交";
}
idempotentChecker.mark(uniqueId);
4. 系统要有“优雅降级”的准备
一旦系统真的撑不住,我们要有预案,比如:
- 自动切换静态页展示
- 关闭非核心功能(如推荐算法)
- 告诉用户“系统繁忙,请稍后再试”
这些看似“失败”的措施,其实是保障用户体验的关键。
5. 技术选型要贴近业务实际
别盲目追求最新技术。如果你的团队对某种组件不熟悉,硬上反而可能适得其反。比如我们可以选择RabbitMQ而不是Kafka,只要能满足当前业务需求即可。
写在最后:高并发不是终点,而是起点
回顾这几年的经历,我发现所谓的“高并发”其实并不是目的,而是手段。它的本质是为了让用户在面对海量请求时,依然能够获得流畅、稳定的服务体验。
也许你会问:“我们公司现在业务量不大,是不是不需要搞这么复杂?”我以前也有这样的想法,直到那次秒杀活动彻底改变了我的认知。技术债越早还,代价越小;系统容量越早规划,未来越轻松。
希望这篇文章能带给你一点启发。如果你正在经历高并发系统的设计或重构阶段,欢迎留言交流。我们一起在这条路上继续前行,打造更稳定、更高效的后端系统。
📌 结语
正如《高性能网站建设指南》中说的那样:“性能就是用户体验”。愿我们都能写出既快又稳的系统,给用户带来更好的体验,也让自己更有成就感。
如果你觉得这篇文章对你有帮助,不妨点个赞或收藏,让更多人看到它。技术的成长从来都不是一个人的事,而是一群人的共同进步。
Stay hungry,stay geek ✨

评论 0