1、缓存与数据库一致性概述:为什么需要一致性、CAP理论在缓存中的应用、常见的缓存策略介绍
大家好,欢迎来到咱们这门课的第一讲。
说实话,缓存与数据库一致性问题,是我在分布式系统里踩过最多的坑之一。我记得刚带团队那会儿,线上一个订单数据对不上,排查了整整两天,最后发现是缓存没及时更新。嗯,从那以后,我对这个问题的敬畏心就上来了。
1.1 为什么需要一致性?
先问个问题:我们为什么要用缓存?
说白了,就是为了扛住高并发,让系统响应更快。但缓存一上,问题就来了——数据库里的数据变了,缓存里的还是老数据。用户读到旧数据,轻则显示异常,重则下单出错、库存对不上。
我在项目中遇到过最典型的一个场景:秒杀活动。用户抢到商品,库存扣了,但缓存没更新。结果下一个用户看到的还是“有货”,点进去却提示“库存不足”。你说用户骂不骂娘?
所以,一致性不是理论问题,是实实在在的线上事故。
核心观点:缓存一致性,本质上是“读”和“写”之间的时间差问题。我们要做的,就是把这个时间差压缩到业务可接受的范围。
1.2 CAP理论在缓存中的应用
聊一致性,绕不开CAP理论。你想想看,分布式系统里,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance),三者只能选其二。
缓存系统也不例外。我们通常选择AP(可用性+分区容错性),牺牲强一致性。为什么?因为缓存本身就是用来扛高可用的,你让它为了强一致性去锁住、去等待,那还不如直接查数据库。
| 场景 | 选择 | 说明 |
|---|---|---|
| 秒杀、抢购 | AP | 允许短暂不一致,保证用户能抢到 |
| 支付、转账 | CP | 必须强一致,宁可失败也不出错 |
| 用户信息展示 | AP | 几秒延迟可以接受 |
我个人习惯是:先判断业务能不能接受“最终一致性”。如果能,那就用缓存;如果不能,那就别用缓存,直接查库。别为了炫技把系统搞复杂了。
避坑指南:我曾经在一个金融项目里,硬要在缓存里做强一致性,结果引入了分布式锁,性能直接掉了80%。后来老老实实去掉缓存,反而更稳。记住:缓存不是万能的。
1.3 常见的缓存策略介绍
好,既然我们接受了最终一致性,那具体怎么实现?这里介绍几种我常用的策略。
1.3.1 Cache-Aside(旁路缓存)
这是最经典的策略。读的时候先查缓存,没有就查数据库,然后回填缓存。写的时候先更新数据库,再删除缓存。
// 读操作
public String get(String key) {
String value = cache.get(key);
if (value == null) {
value = db.get(key);
cache.set(key, value);
}
return value;
}
// 写操作
public void update(String key, String newValue) {
db.update(key, newValue);
cache.delete(key); // 删除缓存,而不是更新
}
为什么写的时候要删除缓存而不是更新?我解释一下:更新缓存可能产生“脏数据”。比如两个并发写操作,一个写A,一个写B,顺序乱了,缓存里可能存了A写B的数据。删除缓存,下次读的时候自然拉新的,更安全。
注意:Cache-Aside存在一个经典问题——删除缓存后、更新数据库前,如果有读请求进来,会把旧数据回填到缓存。这叫“缓存击穿”。解决办法是加延迟双删,或者用消息队列异步同步。
1.3.2 Read-Through / Write-Through
这种策略把缓存当成一个“代理”。读和写都先经过缓存,由缓存去同步数据库。好处是业务代码简单,坏处是缓存组件本身要承担同步逻辑,复杂度上移。
我在项目中用过一次,后来放弃了。因为一旦缓存挂了,整个读写链路都断了。还是Cache-Aside更灵活,缓存挂了还能直接查库。
1.3.3 Write-Behind(异步回写)
写操作只更新缓存,然后异步批量写入数据库。性能极高,但风险也大——如果缓存宕机,数据就丢了。
说实话,这个策略我一般只在日志、统计这类不敏感数据上用。核心业务数据,我建议慎用。
1.3.4 策略对比
| 策略 | 一致性 | 性能 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| Cache-Aside | 最终一致 | 高 | 低 | 大部分业务场景 |
| Read/Write-Through | 强一致 | 中 | 中 | 需要缓存代理的场景 |
| Write-Behind | 弱一致 | 极高 | 高 | 日志、统计等非核心数据 |
我个人最推荐的是Cache-Aside + 延迟双删。简单、可靠、可控。后面几章我会详细讲延迟双删的实现细节,以及怎么用消息队列保证最终一致性。
小结一下:缓存一致性没有银弹。你得根据业务场景选策略,然后接受“最终一致”这个事实。别追求完美,追求可用。
好,这一章就到这里。下一章我们深入聊聊“延迟双删”的具体实现,以及我踩过的那些坑。到时候见。