旁路缓存模式的核心思想是:将缓存视为一个独立的、速度更快的辅助系统,应用程序直接负责与缓存和数据库进行交互。 缓存是数据库的“旁路”,而不是数据库的代理。
其基本原则是:
- 读取时,先读缓存,缓存未命中再读数据库。
- 写入时,直接更新数据库,然后使缓存中的旧数据失效。
详细流程分解
1. 读请求流程
当一个读取请求到达时,流程如下:

步骤解析:
- 接收读请求:应用程序接收到一个根据Key读取数据的请求。
- 尝试从缓存读取:应用程序首先查询缓存(如 Redis, Memcached)中是否存在这个Key的数据。
- 缓存命中:如果缓存中存在该数据,应用程序直接返回缓存中的数据。流程结束。
- 缓存未命中:查询主数据库,把从数据库中查到的数据写入缓存中。
为什么要在缓存未命中后写入缓存?
这是一种“懒加载”思想,只有被实际请求的数据才会被加载到缓存中,避免了冷数据占用宝贵的缓存空间。
2. 写请求流程
当一个写入请求到达时,流程如下:

步骤解析:
- 接收写请求:应用程序接收到一个写入数据的请求。
- 更新主数据库:应用程序直接更新主数据库中的数据。这是唯一的事实来源。
- 删除缓存数据:在成功更新数据库之后,应用程序删除缓存中对应的Key。
为什么是“删除”而不是“更新”缓存?主要为了避免数据竞争和浪费:
- 数据竞争:在高并发场景下,两个并发的写操作可能以不同的顺序完成“更新数据库”和“更新缓存”这两个步骤,导致缓存中最终留下的是旧数据。例如:请求A和B同时更新同一条数据。A先更新缓存,B先更新数据库。最终数据库是B的值,但缓存是A的值,数据不一致。
- 浪费:如果一次写入后,该数据很长时间不被读取,那么这次“更新缓存”的操作就是浪费资源的。直接删除它,等到下次需要时再加载(懒加载),更高效。
旁路缓存模式的优缺点
优点
- 简单直观:逻辑清晰,易于理解和实现。
- 缓存命中率高:通过“懒加载”,缓存中存放的都是真正被请求的热点数据。
- 容错性好:如果缓存集群完全崩溃,系统仍然可以通过直接访问数据库来继续工作(尽管性能会下降)。数据库是系统的“压舱石”。
- 避免写操作的复杂性:写操作只需要处理数据库和删除缓存,避免了同时更新缓存和数据库可能带来的复杂一致性问题。
缺点与挑战
-
缓存不一致窗口期:在写操作中,从“更新数据库”到“删除缓存”之间有一个极短的时间窗口,在这期间缓存是旧数据,数据库是新数据。如果此时有读请求,会读到旧数据。
- 解决方案:可以通过设置较短的缓存过期时间作为兜底策略,或者使用更复杂的机制(如订阅数据库binlog来删除缓存)。
-
缓存穿透:当一个数据在数据库和缓存中都不存在时,每次请求都会穿透到数据库,可能导致数据库压力过大。
-
解决方案:对于不存在的数据,也在缓存中设置一个空值(如
NULL)并设置一个较短的过期时间。
-
解决方案:对于不存在的数据,也在缓存中设置一个空值(如
-
缓存击穿:某个热点Key在缓存过期的瞬间,有大量请求同时涌入,全部穿透到数据库。
- 解决方案:使用互斥锁(分布式锁),只让一个请求去数据库加载数据,其他请求等待。
-
缓存雪崩:在同一时间大量缓存Key过期,导致所有请求都涌向数据库。
- 解决方案:给缓存Key设置随机的过期时间,避免同时大量失效。
总结
旁路缓存模式是一种非常经典和实用的缓存设计模式。它的核心在于 “读时懒加载,写时删缓存” 。虽然它无法实现绝对的强一致性,存在一个短暂的不一致窗口,但其简单性、高容错性和高命中率使其成为绝大多数业务场景的首选方案。在实际应用中,需要结合缓存穿透、击穿、雪崩等问题的解决方案来构建一个健壮的缓存系统。