高并发系统设计:从理论到实践
上周五晚上,我又一次在工位上敲代码到十一点。办公室只剩我和运维老张(他是因为生产环境又炸了才被迫留下的)。窗外下着小雨,咖啡机早就断电,我盯着屏幕上那堆“503 Service Unavailable”的日志,心里默默问候产品经理全家——这需求明明是三天前才提的,非得赶在下周大促上线,还说“只是加个按钮而已”。
我是谁?一个在某二线互联网公司摸爬滚打三年多的Java后端,日常主力工具是 VSCode + 一堆插件(比如 GitHub Copilot、Lombok、Rainbow Brackets),最近疯狂研究 AI 编程助手,因为……嗯,打算跳槽了。现在这家公司文化有点僵化,每次提架构优化都被说“能跑就行”,但大促一来就全员救火,搞得人身心俱疲。
所以,今天这篇文章,不灌鸡汤,不讲空话,就把我这几年踩过的坑、看过的书、试过的产品、写过的 Spring Boot 项目,以及为啥连区块链都扯进来了,一股脑儿倒出来。希望能帮到和我一样在高并发泥潭里挣扎的兄弟。
为什么突然要搞高并发?
起因其实很现实:我们公司的核心交易服务,在去年双11当晚 QPS 突破 8w,直接把 MySQL 主库干崩了。DBA 老王一边骂娘一边扩容,结果 Redis 缓存穿透又引发雪崩,整个下单链路瘫痪了快一个小时。
复盘会上,CTO 黑着脸说:“你们后端是不是没看过《高性能MySQL》?” 我心想:看过啊!但书上写的是一回事,线上跑的是另一回事。更惨的是,我们的 Spring Boot 应用还是单体架构,所有业务逻辑塞在一个 jar 包里,改一行代码就得全量发布——简直是 CI/CD 的噩梦。
于是,领导拍板:“重构!必须支持 10w+ QPS!”
我:“……行吧,反正简历也投出去了。”
从书本到实战:那些被现实打脸的“最佳实践”
1. 读缓存?别只想着 Redis!
刚接手这个任务时,我信心满满地翻开了《高并发系统设计》(阿里出品,确实干货)。第一章就讲缓存策略。我心想:简单,上 Redis + 本地缓存(Caffeine)双层缓存,热点数据扛住!
结果第一版上线后,测试同学反馈:某些商品详情页偶尔返回旧数据。查了一圈才发现,本地缓存和 Redis 没做一致性同步。更离谱的是,有次运维误删了 Redis key,而本地缓存还没过期,导致脏数据持续了整整 5 分钟。
教训:缓存不是万能的,一致性比性能更重要。后来我们做了:
- 写操作时,先更新 DB,再删除缓存(Cache-Aside)
- 本地缓存 TTL 设置为 Redis 的 70%,避免长期不一致
- 加了熔断机制:如果 Redis 连续失败 3 次,自动降级到直连 DB(虽然慢点,但至少数据准)
// Spring Boot 中使用 Caffeine + Redis 双缓存示例
@Service
public class ProductCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private final Cache<String, Product> localCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(30, TimeUnit.SECONDS) // 注意:比Redis短
.build();
public Product getProduct(Long id) {
String key = "product:" + id;
// 先查本地
Product product = localCache.getIfPresent(key);
if (product != null) return product;
// 再查 Redis
product = (Product) redisTemplate.opsForValue().get(key);
if (product != null) {
localCache.put(key, product);
return product;
}
// 最后查 DB
product = productMapper.selectById(id);
if (product != null) {
redisTemplate.opsForValue().set(key, product, 5, TimeUnit.MINUTES);
localCache.put(key, product);
}
return product;
}
}
2. 数据库分库分表?别被 MyBatis Plus 宠坏了
我们之前用 MyBatis Plus,各种 lambdaQuery() 看似优雅,实则暗藏杀机。当订单表数据达到 5000w 行时,一个简单的 count(*) 都要跑十几秒。
看了《数据库系统概念》和《MySQL技术内幕》,决定上 ShardingSphere。但问题来了:原来的业务代码全是耦合的,连分页查询都用的 PageHelper,根本不支持分片。
折腾两周后,终于把订单、用户、商品三大核心表做了水平分片(按 user_id 哈希)。但测试发现:跨分片 join 查询直接爆炸。最后只能妥协——放弃复杂关联查询,改用宽表 + 异步同步(用 Canal 监听 binlog)。
| 方案 | QPS | 响应时间 | 开发成本 | 运维复杂度 |
|---|---|---|---|---|
| 单库单表 | ~2k | 50ms | 低 | 低 |
| 读写分离 | ~5k | 30ms | 中 | 中 |
| 分库分表 | ~20k | 15ms | 高 | 高 |
血泪建议:分库分表不是银弹!如果 QPS 不到 1w,优先考虑读写分离 + 缓存 + SQL 优化。别为了“高大上”硬上分片,后期维护成本会让你哭着求 DBA 收留你。
Spring Boot 真的适合高并发吗?
很多人喷 Spring Boot “太重”,不适合高并发。但我想说:工具没问题,问题是人怎么用。
我们项目最初用的 Tomcat,默认线程池 200,结果压测时线程全堵在 I/O 上。后来换成 Undertow,配合异步 Servlet + CompletableFuture,吞吐量直接翻倍。
# application.yml
server:
undertow:
io-threads: 16
worker-threads: 256
buffer-size: 1024
direct-buffers: true
另外,别滥用 @Transactional!有一次一个接口里套了三层事务,每层都查 DB,结果锁等待超时,整个服务卡死。后来我们定下规矩:
- 事务方法必须标注
timeout - 禁止在循环里调用事务方法
- 读多写少的场景,用
@Transactional(readOnly = true)
还有,日志别乱打!log.info("user {} bought product {}", userId, productId) 看似无害,但在 10w QPS 下,光字符串拼接就能吃掉 20% CPU。后来全改成占位符 + 异步日志(Logback + AsyncAppender),CPU 使用率立马降了 15%。
区块链?你没看错,它居然帮了忙
说到这儿你可能懵了:高并发和区块链有毛关系?
事情是这样的:我们有个“积分兑换”模块,用户频繁操作,但又要保证幂等性和审计追踪。传统方案是加唯一索引 + 重试机制,但分布式环境下还是会出现重复扣减。
某天我刷 Hacker News,看到一篇讲“用区块链思想做状态机”的文章。灵光一闪:能不能把每次积分变动当作一个“区块”,记录前一个状态 hash,形成不可篡改的链?
于是我们用轻量级方式实现了一个 伪区块链:
- 每次积分变更生成 transactionId + previousHash
- 存入单独的 audit_log 表
- 校验时递归验证 hash 链
虽然没用真正的区块链(比如 Ethereum),但这种链式校验思想极大提升了数据可靠性。而且因为只存 hash,存储开销几乎可以忽略。
CREATE TABLE point_transaction (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
amount INT NOT NULL,
previous_hash CHAR(64), -- SHA256 of last record
current_hash CHAR(64), -- SHA256(this record + previous_hash)
created_at TIMESTAMP
);
产品经理看到后惊呼:“我们是不是要发币了?”
我:“……你再提发币,我就把你电脑格式化。”
产品思维:高并发不是技术自嗨
最后想说点题外话。很多工程师(包括我以前)总以为高并发就是堆技术:上 Kafka、换 Redis Cluster、搞 Service Mesh……
但真正决定系统成败的,往往是产品设计。
举个例子:我们有个“秒杀”功能,最初设计是用户点击“抢购”就直接扣库存。结果大促时,几万人同时点,DB 直接锁死。
后来产品改了规则:
- 提前 10 分钟开放“预约”
- 秒杀开始时,只允许预约用户进入
- 实际下单走异步队列
QPS 瞬间从 8w 降到 5k,系统稳如老狗。运维老张那天居然请我喝了杯瑞幸。
所以,下次接到“高并发”需求,先问清楚:
- 用户行为是什么?(是真并发还是可削峰?)
- 数据一致性要求多高?(最终一致可接受吗?)
- 有没有业务手段分流?(比如限流、排队、预加载)
技术永远服务于业务,别让“炫技”毁了你的系统。
总结:我的三条保命经验
折腾半年,系统终于能扛住 12w QPS(压测数据),线上零重大事故。总结下来,就三点:
缓存要谨慎,一致性优先
别盲目加缓存,先想清楚失效策略、穿透防护、雪崩预案。我们现在的缓存命中率 98%,但花了两个月调优。异步化 + 削峰填谷是王道
所有非核心链路(发短信、记日志、更新推荐)全部扔进 Kafka,主流程只干最关键的事。Spring Boot + Spring Kafka 配置起来比想象中简单。监控和告警比代码更重要
上了 Prometheus + Grafana + ELK 后,终于能在故障发生前收到预警。有次凌晨 3 点收到“Redis 连接池耗尽”告警,远程登录扩容,避免了一次 P0 事故。
至于要不要跳槽?说实话,这次重构让我技术成长飞快,简历上也能写“主导高并发系统设计”。但公司给的奖金……emmm,只够买两台 Switch。
不过无所谓了。下周面试一家做 Web3 基础设施的 startup,他们说要用 Spring Boot 构建去中心化交易所后端——又是高并发 + 区块链,看来我这套经验还能接着用。
对了,如果你也在啃《高并发系统设计》这本书,记得配合实践。书是骨架,血肉得自己长。就像我桌上那杯凉透的咖啡,理论再香,也得自己熬。
共勉。

评论 0