【分布式】数据库和缓存双写一致性方案解析

  

  引言   

  

  为什么写这篇文章吗?   

  

  首先,缓存由于其高并发和高性能的特性,已经在项目中被广泛使用。在读取缓存方面,大家没啥疑问,都是按照下图的流程来进行业务操作。   

  

  
  

  

  【分布式】数据库和缓存双写一致性方案解析   

  

  但是在更新缓存方面,对于更新完数据库,是更新缓存呢,还是删除缓存。又或者是先删除缓存,再更新数据库,其实大家存在很大的争议。目前没有一篇全面的博客,对这几种方案进行解析。于是博主战战兢兢,顶着被大家喷的风险,写了这篇文章。   

  

  文章结构   

  

  本文由以下三个部分组成   

  

  1、讲解缓存更新策略   

  

  2、对每种策略进行缺点分析   

  

  3,针对缺点给出改进方案   

  

  正文   

  

  先做一个说明,从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。这种方案下,我们可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。因此,接下来讨论的思路不依赖于给缓存设置过期时间这个方案。   

  

  在这里,我们讨论三种更新策略:   

  

  先更新数据库,再更新缓存   

  

  先删除缓存,再更新数据库   

  

  先更新数据库,再删除缓存   

  

  应该没人问我,为什么没有先更新缓存,再更新数据库这种策略。   

  

  (1)先更新数据库,再更新缓存   

  

  这套方案,大家是普遍反对的。为什么呢?有如下两点原因。   

  

  原因一(线程安全角度)   

  

  同时有请求一个和请求B进行更新操作,那么会出现   

  

  (1)线程一个更新了数据库   

  

  (2)线程B更新了数据库   

  

  (3)线程B更新了缓存   

  

  (4)线程一个更新了缓存   

  

  这就出现请求一个更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B却比一个更早更新了缓存。这就导致了脏数据,因此不考虑。   

  

  原因二(业务场景角度)   

  

  有如下两点:   

  

  (1)如果你是一个写数据库场景比较多,而读数据场景比较少的业务需求,采用这种方案就会导致,数据压根还没读到,缓存就被频繁的更新,浪费性能。   

  

  (2)如果你写入数据库的值,并不是直接写入缓存的,而是要经过一系列复杂的计算再写入缓存。那么,每次写入数据库后,都再次计算写入缓存的值,无疑是浪费性能的。显然,删除缓存更为适合。   

  

  接下来讨论的就是争议最大的,先删缓存,再更新数据库。还是先更新数据库,再删缓存的问题。   

  

  (2)先删缓存,再更新数据库   

  

  该方案会导致不一致的原因是。同时有一个请求一进行更新操作,另一个请求B进行查询操作。那么会出现如下情形:   

  

  (1)请求一进行写操作,删除缓存   

  

  (2)请求B查询发现缓存不存在   

  

  (3)请求B去数据库查询得到旧值   

  

  (4)请求B将旧值写入缓存   

  

  (5)请求一个将新值写入数据库   

  

  上述情况就会导致不一致的情形出现。而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。   

  

  那么,如何解决呢?采用延时双删策略   

  

  伪代码如下   

  

  publicvoidwrite (String 关键,Object 数据){   

  

  redis.delKey(关键);   

  

  db.updateData(数据);   

  

  thread . sleep (1000);   

  

  redis.delKey(关键);   

  

  }   

  

  转化为中文描述就是   

  

【分布式】数据库和缓存双写一致性方案解析