写缓存和写数据,因为不是原子性的,所以会造成数据的不统一。那咱们是先写数据库还是先操作缓存呢,缓存操作是更新还是删除呢?
任何脱离业务的设计都是耍流氓。咱们试想一个场景,A服务更新缓存后,很长一段时间内没有人拜访,B服务也更新了缓存,那A更新的缓存不就做了无用功了?更甚的是,如果这个缓存的计算,是极其简单的、耗时的、耗资源的,多来几个操作数据库的操作,这个开销就很大了。所以咱们选的是做删除,只有用到的时候,才去计算缓存,没有用到缓存的时候,不做计算,毛病就是第一次拜访可能会比较慢。
那当初剩下两种状况:

  1. 先写数据库,再删除缓存。
  2. 先删除缓存,再写数据库。

另外,咱们也从并发和故障两种状况来思考。

先数据库

并发

如下图所示:

  1. 服务A更新数据,把100更改为99,而后删除缓存。
  2. 服务B读取缓存,此时缓存为空,从数据库读取99。
  3. 服务C更新数据,把99更改为98,而后删除缓存。
  4. 服务B赋值缓存为99。

此时,数据库为98,然而缓存为99,数据不统一。

故障

如下图所示:

  1. 服务A更新数据,把100更改为99。
  2. 服务A删除缓存失败。
  3. 服务B读取缓存,为100。

此时缓存数据和数据库数据不统一

先缓存

并发

如下图所示:

  1. 服务A删除缓存。
  2. 服务B读取缓存。
  3. 服务B读取不到缓存,就读取数据库,此时为100。
  4. 服务A更新数据库。

此时缓存数据和数据库数据不统一

故障

如下图所示:

  1. 服务A更新缓存。
  2. 服务A更新数据库失败。
  3. 服务B读取缓存。
  4. 服务B读取数据库。

因为数据库未做更改,所以缓存数据和数据库数据统一。

解决方案

业务

任何脱离业务的设计都是耍流氓。如果业务能够承受这种不统一,那就采纳Cache Aside Pattern。如果业务上不承受,那能够让强一致性需要的业务间接读数据库,然而这个性能就会很差,所以咱们有以下的串行计划。

串行

在java中,多线程会有数据安全问题,咱们能够用synchronized和cas来解决,次要的思路就是把并行改串行。
思路一:通过音讯队列
把所有的申请压入队列,因为申请的key太多,能够依照hash取模的形式,把key扩散到无限的队列中。比方有3个队列,hash(key)=0,存入第一个队列。
存入的是读、读、写、读、读、写,那执行的时候,跟存入的程序是一样的,因为对同一个key的操作是串行的,所以保障了的数据的一致性。

思路二:通过分布式锁
在下面的计划中,如果队列太多,则不好治理,如果队列太少,则那么多的key在排队影响了性能,所以咱们能够采纳分布式锁来做。此时的办法相似于synchronized和cas,只有获取到了锁,才能够操作,这样也能够保证数据的一致性。
当然不论采纳哪种形式,数据的一致性和性能的关系是正比的。数据的一致性要求高,性能就会降落;数据的一致性要求不高,性能就会回升。