缓存问题

缓存雪崩、缓存击穿、缓存穿透

详细见:缓存雪崩、缓存击穿和缓存穿透

我这里主要是谈一谈,如何理解性记忆这三种缓存失效情况。

缓存穿透:请求的数据既不在缓存中,也不在数据库中(访问的数据压根就没有存在过),导致每次请求都会穿透缓存访问数据库,增加后端数据库的压力

缓存击穿:缓存中某些热点数据缓存失效时(过期了),多个请求同时访问这个缓存,导致多个请求同时打到数据库或后端服务上,形成“击穿”现象

缓存雪崩:缓存中的大量数据在同一时间失效,导致大量请求直接访问数据库或后端服务,给数据库带来巨大的压力,可能导致数据库崩溃

image20250228191204802.png

缓存与数据库一致性问题

首先需要取得以下两点共识:

  • 缓存必须有过期时间
  • 保证数据库与缓存的最终一致性即可,不必追求强一致性

旁路缓存

也称为手动加载缓存。读取缓存、读取数据库和更新缓存的操作都在应用系统中完成,是业务系统最常用的缓存策略。

在这种策略中,缓存的加载和维护是由应用程序负责的,而不是由缓存系统自动管理的。

读取数据流程:

  1. 缓存命中:应用程序需要读取数据时,先检查 Redis 缓存是否命中,如果命中就直接返回给应用程序
  2. 缓存未命中:如果数据不在 Redis 中(缓存未命中),那么应用程序将从数据库中获取数据
  3. 复制数据到 Redis:把数据库查询到的数据复制到 Redis 中,以便后续读取相同数据会命中 Redis 缓存
  4. 返回数据:应用程序将获取到的数据返回给调用方

写数据流程:

  1. 应用程序更新数据库数据
  2. 手动更新或删除相应的缓存项,以确保缓存与数据库保持一致

注:通常选择删除缓存,因为更新缓存的成本很高,可能需要访问多张表联合计算。

问题分析

详细见:数据库和缓存如何保持一致性?

旁路缓存存在一个问题,由于是手动管理,存在数据在数据存储中更新但缓存未及时更新的情况,导致缓存与数据存储不一致。

在使用旁路缓存策略时,对于写操作推荐先更新数据库,再删除缓存的方案。即先更新数据库中的数据,数据更新成功后,再删除缓存中的相关条目,下次读取时缓存会重新加载数据。

缓存与数据库一致性解决方案

既然如此,我们就得保证[更新数据库,再删除缓存]是原子操作,可能会发生数据库更新成功但缓存删除失败的情况。

下面介绍两种方案,归根结底就是把操作的数据行为另存一份,直到保证两个操作成功再移除,否则多次重试。

第一种:[删除缓存]重试机制

删除缓存失败,就异步发送需要删除的 key 到消息队列中。如果删除失败且未达到重试最大次数则将消息重新加入消息队列,直到删除成功。

如果超过指定重试次数,还没有成功的话,就记录到数据库,人工介入。

这个方案的缺点是,对代码入侵性比较强,需要改造原本业务的代码。

第二种:读取 MySQL binlog 异步删除 Redis 缓存

  1. 更新数据库。数据库会把操作信息记录在 binlog 日志中
  2. 使用 canal 订阅 binlog 日志获取目标数据和 key
  3. 缓存删除系统 获取 canal 的数据,解析目标 key,尝试删除缓存
  4. 如果删除失败则将消息发送到 消息队列
  5. 缓存删除系统重新从消息队列获取数据,再次执行删除操作
b08e4180a2f96663d0d66f3a5175a77a.png

Canal 模拟 MySQL 主从复制的交互协议,把自己伪装成一个 MySQL 的从节点,向 MySQL 主节点发送 dump 请求,MySQL 收到请求后,就会开始推送 Binlog 给 Canal,Canal 解析 Binlog 字节流之后,转换为便于读取的结构化数据,供下游程序订阅使用。