Skip to content

如何保证Redis与MySQL的数据一致性

旁路缓存(Cache-Aside)的执行策略

旁路缓存(Cache-Aside)是目前业务系统中最广泛使用的缓存策略。其核心思想是,应用程序需要自行维护缓存和数据库的操作,缓存系统本身不参与和数据库的协同。

具体执行策略分为读取和更新两个操作:

1. 读取数据策略: * 查询缓存: 当应用程序需要读取数据时,它首先会去查询缓存。 * 缓存命中: 如果在缓存中找到了所需的数据(即“缓存命中”),则直接将数据返回给应用。 * 缓存未命中: 如果缓存中没有数据(即“缓存未命中”),应用程序会转而去查询数据库。 * 更新缓存: 从数据库中获取到数据后,应用程序会将这份数据写入到缓存中,以便下一次相同的请求能够直接从缓存中获取。 * 返回数据: 将从数据库中读取的数据返回给应用。

2. 更新数据策略: 当需要更新数据时,为了保证数据的一致性,最常用的方式是: * 先更新数据库: 将新的数据写入到数据库中。 * 再删除缓存: 成功更新数据库后,将缓存中对应的旧数据删除(使其失效)。

为什么是删除缓存而不是更新缓存? 文章中提到,采用“删除”而非“更新”缓存主要有两个优势: * 性能优势: 如果更新缓存的成本很高,而该缓存的访问频率又不高,那么直接删除会更高效。等到下次需要访问时,再通过读取操作从数据库加载新数据到缓存中,避免了不必要的更新开销。 * 安全优势: 在高并发场景下,如果采用“更新缓存”的策略,多个线程的更新顺序可能会导致缓存中写入脏数据。而“删除缓存”能更简单有效地避免这个问题。

如何保证数据一致性?

先更新数据库,再删除缓存这一策略是旁路缓存(Cache-Aside)模式中,为了保证数据一致性而推荐的写入(更新/删除)操作流程。

当需要更新数据时,整个操作分为明确的两步: 1. 第一步:更新数据库

- 应用程序接收到数据更新的请求(例如,修改用户信息、更新商品库存等)。

- 它会直接对数据库执行UPDATE或DELETE等写操作。
  1. 第二步:删除缓存

    • 在数据库成功完成更新操作之后,应用程序会向缓存系统(如 Redis)发送一个DELETE命令,将与该数据对应的缓存项删除。

    • 关键点:这里是删除缓存,而不是更新缓存。

延迟双删策略 该策略主要用于应对“先删除缓存,再更新数据库”场景下可能出现的数据不一致问题。在高并发时,可能存在一个请求删除了缓存,但数据库还未更新完成时,另一个读请求将数据库的旧值又加载到了缓存中,随后数据库才完成更新,导致不一致。 延迟双删的步骤如下: * 第一次删除: 在更新数据库之前,先删除一次缓存。 * 更新数据库: 执行数据库的更新操作。 * 延迟后第二次删除: 在数据库更新成功后,等待一段短暂的时间(如几百毫秒),再次删除缓存。这次删除是为了确保在数据库更新期间进入的读请求所加载的旧缓存数据能够被清除。

缓存删除重试保障机制 即使是“先更新数据库,再删除缓存”的策略,也可能因为网络问题等原因导致缓存删除失败。为了确保缓存最终能被成功删除,可以引入重试机制。 一种常见的做法是: * 将删除失败的缓存键(key)发送到消息队列(MQ)中。 * 创建一个独立的服务来消费这个队列。 * 该服务会不断尝试重新删除缓存,直到成功为止。这种异步化的方式可以作为一种兜底方案,确保数据最终的一致性。

订阅数据库变更日志(binlog) 这是一种将缓存同步操作与业务逻辑解耦的强大方案。 * 数据库记录变更: 数据库(如MySQL)在执行数据更新后,会将变更记录到其二进制日志(binlog)中。 * 中间件订阅: 使用像canal这样的工具来订阅和解析数据库的binlog。 * 独立服务处理: 一个独立的消费者服务获取到canal解析出的数据变更信息(比如被修改的数据的key)。 * 删除缓存: 该服务根据获取到的key,去执行删除缓存的操作。