高并发下如何保证缓存和数据库的数据一致性?


缓存由于其高性能 , 支持高并发的特性 , 在高并发的项目中不可或缺 。 被大家广泛使用的有Redis , Memcached等 。 本文主要探讨几种常见的缓存的读写模式 , 以及如何来保证缓存和数据库的数据一致性 。
Cache-Aside
Cache-Aside可能是项目中最常见的一种模式 。 它是一种控制逻辑都实现在应用程序中的模式 。 缓存不和数据库直接进行交互 , 而是由应用程序来同时和缓存以及数据库打交道 。 Cache-Aside的名字正体现了这个模式 , Cache在应用的一旁(aside) 。
读数据时

  1. 程序需要判断缓存中是否已经存在数据 。
  2. 当缓存中已经存在数据(也就是缓存命中 , cache hit) , 则直接从缓存中返回数据
  3. 当缓存中不存在数据(也就是缓存未命中 , cache miss) , 则先从数据库里读取数据 , 并且存入缓存 , 然后返回数据

高并发下如何保证缓存和数据库的数据一致性?
本文插图
写数据时 , 我们可以有以下两种策略:
第一种策略:
  1. 更新数据库
  2. 更新缓存
但这种策略有线程安全的问题 , 可能出现缓存和数据库不一致 。 试想有两个写的线程 , 线程A和线程B
  1. A写数据库
  2. B后于A写数据库
  3. B写缓存
  4. A写缓存
  5. 缓存和数据库中的数据不一致 , 缓存中的是脏数据
要解决线程安全的问题 , 我们可以加锁 , 不过实现起来比较麻烦 , 因此我们不考虑这种写策略 , 而使用第二种策略 。
第二种策略:
  1. 更新数据库
  2. 删除缓存中对应的数据

高并发下如何保证缓存和数据库的数据一致性?
本文插图
那么这种写策略会有线程安全的问题吗?有 , 试想一下有两个线程 , 线程A读 , 线程B写
  1. A读数据 , 由于未命中那么从数据库中取数据
  2. B写数据库
  3. B删除缓存
  4. A由于网络延迟比较慢 , 将脏数据写入缓存
但是这种情况可能性非常的小 , 需要同时满足很多条件 , 近乎不太可能发生 , 所以我们一般都采用这种写策略 。 另外可以对缓存中的数据设置合适的过期时间 , 即使发生的脏数据的情况 , 也不会发生很长时间 。
应用场景
应用于缓存不支持Read-Through/Write-Through的系统 。
优点
  • 缓存仅仅保存被请求的数据 , 属于懒加载模式(Lazy Loading) , 和下文的Write-Through模式相比 , 避免了任何数据都被写入缓存造成缓存频繁的更新 。
缺点
  • 当发生缓存未命中的情况时 , 则会比较慢 , 因为要经过三个步骤:查询缓存 , 从数据库读取 , 写入缓存 。
  • 复杂的逻辑都在应用程序中 , 如果实现微服务 , 多个微服务中会有重复的逻辑代码
Read-Through/Write-Through
这种模式中 , 应用程序将缓存作为主要的数据源 , 而数据库对于应用程序是透明的 , 更新数据库和从数据库的读取的任务都交给缓存来代理了 , 所以对于应用程序来说 , 简单很多 。
Read-Through
由缓存配置一个读模块 , 它知道如何将数据库中的数据写入缓存 。 在数据被请求的时候 , 如果未命中 , 则将数据从数据库载入缓存 。
高并发下如何保证缓存和数据库的数据一致性?
本文插图
Write-Through
缓存配置一个写模块 , 它知道如何将数据写入数据库 。 当应用要写入数据时 , 缓存会先存储数据 , 并调用写模块将数据写入数据库 。
高并发下如何保证缓存和数据库的数据一致性?