高并发系统设计:从理论到实践
作者:老张,三线城市某互联网公司技术负责人,日常边听周杰伦写代码,最近在刷 LeetCode 准备跳槽
上周五晚上十一点半,我坐在办公室里,耳机里放着《稻香》,手边是第 N 杯美式咖啡,盯着线上监控面板上疯狂飙红的 CPU 使用率——又双叒叕崩了。
事情起因是产品经理在周一晨会上轻描淡写地说:“咱们这个新活动预计会有 10w+ 用户同时参与,你们后端应该能扛住吧?”
我当场差点把键盘吞下去。我们团队总共就 5 个人,项目 deadline 是周五上线,而当前系统连 1w 并发都费劲。更离谱的是,这个“活动”居然是个秒杀类功能,还要求实时扣库存、防超卖、支持高并发下单。
这不就是典型的“既要马儿跑,又要马儿不吃草”吗?
但没办法,谁让我是个打工人呢?而且——咳咳——最近正在偷偷准备跳槽,正好借这个机会把高并发这块硬骨头啃下来,面试时也能吹一吹“我亲手扛过 10w QPS 的系统”。
于是,这篇技术分享就诞生了。不是什么高大上的理论堆砌,而是一个三线城市小厂技术负责人,在 deadline 压力下,如何一步步把系统从“脆皮鸡”优化成“铁布衫”的真实记录。
别被“高并发”吓住,它没你想的那么玄
很多人一听到“高并发”,立马想到淘宝双11、抖音春晚红包、比特币矿池……然后觉得自己这辈子也碰不到。但其实,只要你的用户量稍微上来点,或者搞个营销活动,高并发问题就会悄无声息地找上门。
比如我们这次的需求:
- 预计峰值 8w~10w 请求/秒
- 商品库存 1000 件(限量秒杀)
- 要求 99.9% 的请求响应时间 < 200ms
- 不能超卖(否则老板要砍人)
乍一看很吓人,但拆解之后,核心就三点:
- 流量削峰:别让所有请求一股脑砸到数据库
- 库存一致性:扣库存不能多也不能少
- 快速失败 & 降级:扛不住时优雅拒绝,别全挂
说白了,高并发不是堆服务器,而是用聪明的方式“偷懒”。
架构演进:从单体到分层防御
我们的原始架构很简单:SpringBoot + MySQL 单库单表,前端直连后端。平时日活几千人,跑得挺欢。但一压测,1000 并发就直接 502。
第一阶段:加缓存(Redis 登场)
最直接的优化当然是把热点数据扔进 Redis。商品信息、库存数量全部缓存,读操作基本不碰 DB。
// 扣库存伪代码(错误示范!)
public boolean deductStock(Long skuId) {
Integer stock = redisTemplate.opsForValue().get("stock:" + skuId);
if (stock > 0) {
redisTemplate.opsForValue().decrement("stock:" + skuId);
return true;
}
return false;
}
结果?超卖了。因为 get 和 decrement 不是原子操作。两个线程同时读到 stock=1,都以为还能买,结果扣成 -1。
血泪教训:缓存不是万能的,并发场景下的原子性必须靠底层保证。
解决方案:用 Redis 的 DECR + WATCH 事务,或者更简单的——Lua 脚本。
-- stock_deduct.lua
local stock = redis.call('GET', KEYS[1])
if tonumber(stock) > 0 then
redis.call('DECR', KEYS[1])
return 1
end
return 0
SpringBoot 调用:
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/stock_deduct.lua")));
script.setResultType(Long.class);
Long result = redisTemplate.execute(script, Collections.singletonList("stock:" + skuId));
if (result != null && result == 1) {
// 扣减成功,后续异步落库
}
这一步搞定后,读写分离 + Redis 原子扣减,系统轻松扛到 5w QPS。
第二阶段:异步化 & 消息队列削峰
但新问题来了:虽然库存扣减快了,可订单创建、日志记录、通知服务这些同步操作还是慢。一旦下游服务抖一下,整个链路就卡住。
这时候就得祭出消息队列(MQ)了。我们选了 RabbitMQ(公司已有基础设施,运维熟悉),把非核心流程异步化:
- 用户请求 → 校验参数 + Redis 扣库存(快)
- 成功则发消息到 MQ → “请创建订单”
- 订单服务消费消息,落库、发通知等(慢但不影响主流程)
@PostMapping("/seckill")
public ResponseEntity<?> seckill(@RequestBody SeckillRequest req) {
if (!redisStockDeduct(req.getSkuId())) {
return ResponseEntity.status(429).body("库存不足");
}
// 快速返回,用户体验好
rabbitTemplate.convertAndSend("seckill.order.queue", req);
return ResponseEntity.ok("抢购成功,请等待订单确认");
}
效果立竿见影:接口 P99 从 800ms 降到 60ms,且即使订单服务挂了,用户也只会收到“稍后处理”的提示,而不是“系统错误”。
吐槽一句:测试同学一开始死活不信“成功了怎么没订单?”,后来我给她画了架构图,她才恍然大悟:“哦~原来你们后端现在玩‘先斩后奏’啊!”
第三阶段:限流 & 熔断,保住底线
再强的系统也有极限。万一真来了 20w QPS(比如被黑产盯上),我们总不能让服务器直接炸掉吧?
所以必须加上限流和熔断机制。
我们用的是 Sentinel(阿里开源,SpringCloud Alibaba 生态,集成方便):
# application.yml
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
datasource:
ds1:
nacos:
server-addr: ${nacos.server-addr}
dataId: ${spring.application.name}-sentinel
groupId: DEFAULT_GROUP
rule-type: flow
然后在 Controller 上加注解:
@SentinelResource(value = "seckill", blockHandler = "handleSeckillBlock")
@PostMapping("/seckill")
public ResponseEntity<?> seckill(...) { ... }
public ResponseEntity<?> handleSeckillBlock(BlockException ex) {
return ResponseEntity.status(429).body("请求太频繁,请稍后再试");
}
配合 Nacos 动态配置规则,比如:
- QPS 阈值:8000
- 超过则快速失败
这样,即使流量洪水来袭,系统也能稳稳守住核心功能,不至于雪崩。
数据库:别让 MySQL 成为瓶颈
前面都是“挡”和“削”,但最终数据还是要落库。如果 DB 挂了,前面的努力全白费。
我们的优化策略:
- 分库分表?暂时不用。库存就 1000 件,订单量也不大,没必要上 ShardingSphere。
- 读写分离:主库写,从库读(但秒杀场景基本全是写,作用有限)。
- 批量插入 + 异步落库:订单服务消费 MQ 后,攒够 100 条再批量 insert,减少 DB 压力。
- 库存表单独拆分:避免和其他业务争抢 InnoDB 行锁。
最关键的一招:预热库存 + 冷热分离。
- 活动开始前,把库存初始化到 Redis 和 DB
- 活动结束后,把“已售罄”的商品标记为冷数据,查询走归档库
-- 库存表结构(简化)
CREATE TABLE seckill_stock (
sku_id BIGINT PRIMARY KEY,
total INT NOT NULL, -- 总库存
locked INT NOT NULL, -- 已锁定(待支付)
version INT DEFAULT 0 -- 乐观锁
);
扣库存时用 CAS + 乐观锁 双保险:
UPDATE seckill_stock
SET locked = locked + 1, version = version + 1
WHERE sku_id = ? AND total - locked > 0 AND version = ?
虽然 Redis 扛了 99% 的流量,但 DB 这道最后防线,必须牢不可破。
区块链?别闹,但可以学它的思路
说到这儿,你可能会问:“标题里不是有‘区块链’吗?怎么还没提?”
哈哈,别急。我们公司当然没上区块链(那玩意儿性能比 MySQL 还低,QPS 几百就顶天了),但区块链的设计思想其实对高并发系统很有启发:
- 去中心化 → 对应我们的“服务自治”:每个微服务只管自己的数据一致性,不依赖全局锁
- 不可篡改 → 对应“操作可追溯”:所有扣库存、创建订单的操作都记日志,便于对账
- 共识机制 → 对应“最终一致性”:允许短暂不一致,但通过补偿机制(如定时对账)保证最终正确
所以,不是要用区块链技术,而是借鉴它的“分布式信任”思维。毕竟,高并发的本质,就是在不确定的环境中建立确定性。
跳槽视角:高并发经验值拉满
说实话,这次项目让我收获最大的,不是技术本身,而是如何在资源有限的小公司,用低成本方案解决大问题。
以前看大厂文章,动不动就 Flink、Kafka、TiDB,觉得高大上。但现实是:三线城市小厂,可能连专职 SRE 都没有,运维就一个人,还得兼 DBA。
所以我们的方案必须满足:
- 简单:技术栈不能太新,否则没人会维护
- 稳定:宁可用成熟的 RabbitMQ,也不上 Pulsar
- 可监控:集成 Prometheus + Grafana,关键指标一目了然
| 指标 | 优化前 | 优化后 |
|---------------------|------------|------------|
| 峰值 QPS | 1,200 | 95,000 |
| 接口 P99 延迟 | 820ms | 58ms |
| 超卖发生次数 | 23 次 | 0 次 |
| 服务器成本 | 4 台 | 6 台(+MQ)|
上周活动上线,平稳度过峰值,老板请全组吃了顿火锅(虽然是公司楼下 88 元套餐)。更重要的是,这段经历写进简历,面试官眼睛都亮了。
最近投了几家一线大厂,聊到高并发,我不再只会背“CAP 理论”,而是能说出:“我们当时用 Redis Lua + MQ 异步 + Sentinel 限流,在 6 台 4C8G 机器上扛住了 10w QPS,超卖为零。”
——这才是真实的、有血有肉的技术成长。
最后几句真心话
高并发系统设计,从来不是炫技,而是在约束条件下做最优权衡。你不需要一开始就设计出“能扛 100w QPS”的系统,但你得知道当流量来临时,哪几根杠杆能最快撬动性能。
对我这种准备跳槽的人来说,动手做过,比背一百篇面经都有用。哪怕是在小公司,只要问题真实、思考深入、方案落地,就是硬核竞争力。
哦对了,活动上线那天晚上,我终于关掉 IDE,摘下耳机,走出公司大楼。抬头看见满天星星,突然觉得——
写代码这事儿,虽然累,但真能改变点什么。
(完)
P.S. 如果你也正在刷题准备跳槽,或者被产品经理的“小需求”折磨得睡不着觉——欢迎留言交流。
P.P.S. 本文所有方案已在生产环境验证,代码片段可直接参考(但别照搬,记得结合自己业务!)
P.P.P.S. 下次想听我聊聊“如何在小公司推动技术升级”吗?点赞过 100 就写!

评论 0