1. 缓存一致性问题概述

为什么需要缓存一致性?

先问大家一个问题:你写代码的时候,有没有遇到过明明刚存进去的数据,读出来却是旧的?

我在2015年做一个分布式KV存储项目时,就踩过这个坑。当时系统跑着跑着,数据就开始"串位"了——节点A写入了"value=100",节点B读到的却是"value=50"。排查了整整两天,最后发现是缓存一致性没处理好。

说白了,缓存一致性要解决的就是一个核心矛盾:数据只有一份,但访问它的路径有很多条

你想想看,现代存储系统里,数据从磁盘到内存,从内存到CPU缓存,从本地节点到远端节点,每一层都在做缓存。缓存越多,性能越好,但数据不一致的风险也越大。

核心观点:缓存一致性不是"要不要"的问题,而是"怎么做到"的问题。只要系统里有多个数据副本,一致性就是绕不开的坎。

缓存一致性问题产生的根本原因

根本原因其实就三个字:共享了

多核CPU共享同一块内存,多节点共享同一份数据。共享本身没问题,问题出在——每个核、每个节点都有自己的"小算盘"(本地缓存)。

我习惯把这个问题拆成两个层面来看:

1. 多核场景下的缓存不一致

先看一个最简单的例子:

// 假设两个CPU核同时执行这段代码
// 变量 x 初始值为 0

// CPU核0执行:
x = 1;

// CPU核1执行:
print(x);  // 可能打印出 0,而不是 1

为什么会这样?因为每个核都有自己的L1/L2缓存。核0把x改成1,但修改还留在自己的缓存里,没写回内存。核1读x的时候,从自己的缓存或者内存里读到的还是0。

嗯,这里要注意:缓存一致性协议(比如MESI)就是用来解决这个问题的。但协议本身也有开销,不是所有场景都能完美覆盖。

2. 多节点场景下的缓存不一致

分布式系统里更复杂。每个节点都有自己的内存和磁盘缓存,节点之间通过网络通信。

我记得有一次做异地多活架构,两个数据中心各自缓存了一份用户数据。结果用户在北京修改了昵称,上海那边读到的还是旧昵称。这就是典型的分布式缓存不一致

场景 不一致原因 典型例子
多核CPU 每个核有私有缓存,写操作未及时同步 MESI协议失效
多节点服务器 各节点独立缓存,网络延迟导致同步滞后 Redis集群缓存雪崩
客户端-服务端 浏览器/CDN缓存与源站数据不一致 静态资源更新后未刷新
数据库与缓存 缓存层与持久层数据不同步 缓存穿透/击穿

缓存一致性的代价

你可能会想:那我把所有缓存都去掉,直接读写原始数据不就行了?

理论上可以,但实际没人这么干。因为没有缓存的系统,性能根本扛不住。我做过测试,一个100万QPS的查询系统,去掉缓存层后直接掉到5万QPS,差了20倍。

所以,缓存一致性本质上是在性能正确性之间找平衡。

我的经验:不要追求100%的强一致性。大多数业务场景下,最终一致性就够用了。比如用户发了一条朋友圈,好友晚几秒看到,没人会在意。但如果是银行转账,那必须强一致。

缓存一致性的分类

业内一般把缓存一致性分成几个等级:

  • 强一致性:写操作完成后,任何后续读操作都能读到最新值。代价是性能最差。
  • 弱一致性:不保证立即读到最新值,但最终会一致。性能最好。
  • 最终一致性:弱一致性的一个特例,保证"如果不再有更新,最终所有读操作都能返回最新值"。

我个人习惯把最终一致性再细分成几个变种:

  1. 因果一致性:有因果关系的操作,顺序必须一致。比如先发帖再评论,别人看到评论时一定也能看到帖子。
  2. 读己之写一致性:用户自己总能读到自己的最新写入。这个在用户系统里特别重要。
  3. 单调读一致性:一个用户多次读取同一个数据,不会读到越来越旧的值。

避坑指南:我曾经在一个金融项目中,为了追求强一致性,给每个写操作都加了分布式锁。结果系统吞吐量直接腰斩,业务方差点没把我骂死。后来改成读己之写一致性,配合版本号校验,既保证了正确性,性能也只下降了15%。

本章知识体系

下面这张图是我自己画的,把缓存一致性的核心脉络梳理了一下:

缓存一致性知识体系 缓存一致性 为什么需要? 根本原因 一致性分类 性能提升 数据正确性 多核共享 多节点共享 强一致性 弱一致性 最终一致性 因果一致性 读己之写 单调读 核心:性能与正确性的平衡艺术

小结

缓存一致性这个问题,说白了就是:数据副本多了,怎么让它们看起来像只有一份

我做了十几年存储系统,最大的体会是:没有银弹。不同的业务场景,要选不同的一致性模型。关键是要搞清楚你的业务到底能容忍多大的不一致,然后选择性价比最高的方案。

下一章我会深入讲MESI协议,那是多核CPU缓存一致性的基石。嗯,先消化这些吧。


公众号:蓝海资料掘金营,微信 deep3321