高并发下如何保证数据的一致性和可靠性
2026/3/19大约 4 分钟面试题面试题
这道题是面试中的“必杀技”。面试官问的不是教科书定义,而是你对性能(Performance)与一致性(Consistency)这两个死对头的权衡取舍 。
核心原则:首先必须明确一个大前提:在绝大多数高并发业务场景中,我们追求的不是不切实际的“强一致性”(Strong Consistency),而是高可用前提下的“最终一致性”(Eventual Consistency)。
第一关:数据库与缓存的一致性(Cache Aside Pattern)
这是最基础的战场。面试官通常会挖三个坑:
1. 是更新缓存还是删除缓存?
- 错误回答:“更新数据库时顺便更新缓存。”
- 致命缺陷:并发写冲突。线程A和线程B同时写,A先写库,B后写库;但B可能先更新缓存,A后更新缓存。结果数据库是B的新值,缓存是A的旧值,导致脏数据。

- 正确姿势:删除缓存。让下一次读请求去回填缓存。
2. 先删缓存还是先写数据库?
- 错误回答:“先删缓存,再写数据库。”
- 致命缺陷:持久性脏数据。线程A删了缓存,还没写库;线程B来读,发现没缓存,去读旧库,把旧数据塞回缓存;然后A才写新数据入库。结果缓存里永远是旧数据 。

- 正确姿势:先写数据库,再删缓存。
3. “延时双删”靠谱吗?
- 陷阱:很多人喜欢说“先删缓存,写库,sleep一下,再删缓存”。
- 致命缺陷:这个
sleep的时间完全靠猜 。睡短了覆盖不了主从同步延迟,睡长了拖垮吞吐量。在生产环境写Thread.sleep是会被拉出去祭天的 。
【架构师级解决方案】:异步解耦(Binlog + MQ) 不要在业务代码里纠结。

- 解耦:业务代码只管写MySQL 。
- 监听:利用 Canal 或 Maxwell 伪装成从库,监听 MySQL 的 Binlog。
- 重试:Binlog 变动触发消息发送到 MQ(Kafka/RocketMQ)。消费者收到消息去删缓存 。
- 保障:利用 MQ 的重试机制保证缓存最终被删除,失败了还可以报警人工介入 。
第二关:微服务间的分布式事务
如果涉及跨服务(如订单服务下单、库存服务扣减),怎么保证一致性?
1. 为什么不用 2PC(两阶段提交)?
- 致命缺陷:同步阻塞。资源锁定时间太长,性能极差。在高并发系统里用 2PC 就是系统的罪人 。
【架构师级解决方案】:RocketMQ 事务消息(半消息机制)这是一套保证最终一致性的“柔性事务”方案 。

- 发送半消息(Half Message):订单服务先发一条消息给 MQ,此时对消费者(库存服务)不可见。
- 执行本地事务:订单服务写自己的数据库(创建订单) 。
- Commit/Rollback:本地事务成功,告诉 MQ “提交”,库存服务才能消费;失败则“回滚” 。
面试官必问:如果本地事务成功了,但提交给 MQ 的确认消息丢了怎么办?
- 杀手锏:事务回查(Back-Check)。
- MQ 发现某条半消息悬在半空很久了,会主动回调生产者接口:“哎,这笔订单到底成没成?” 。
- 你去查本地数据库,如果订单在,就补发 Commit。这就是系统的自我修复机制。

总结
回答这道题的逻辑闭环应该是:
- 放弃强一致性,拥抱最终一致性。
- 用 Binlog + MQ 异步解耦 解决数据库与缓存的同步问题 。
- 用 RocketMQ 事务消息 + 回查机制 解决跨服务的数据一致性问题 。
这才是架构师该有的思维模式:可用性优先,接受软状态,最终一致性兜底。