主从DB与Cache一致性问题
标签:Redis

主从DB与Cache一致性问题

1. 缓存设计一些问题

1.1 “更新缓存”还是“淘汰缓存”

更新缓存:数据不但会写入数据库,还会写入缓存

淘汰缓存:数据只会写入数据库,不会写入缓存,只会把缓存数据淘汰掉

更新缓存优点:缓存不会增加一次miss,命中率高

淘汰缓存优点:对于复杂的操作来说更新更简单

那到底是选择更新缓存还是淘汰缓存呢,主要取决于“更新缓存的复杂度”。

如果只是简单的把余额money设置成一个值,对于淘汰策略为 deleteCache(uid),对于更新策略为 setCache(uid,money),此时更新的代价很小

如果余额是通过很复杂的数据计算得出来的,比如我们要先从数据库中取出商品价格,折扣,还有把原来的余额从缓存中取出来,再把新的缓存余额设置到缓存,这个时候淘汰策略更好。

故:

淘汰缓存操作简单,并且带来的副作用只是增加了一次cache miss,建议作为通用的处理方式。

1.2 先操作数据库 vs 先操作缓存

对于一个不能保证事务性的操作,一定涉及“哪个任务先做,哪个任务后做”的问题,解决这个问题的方向是:

如果出现不一致,谁先做对业务的影响较小,就谁先执行。

假设先写数据库,后淘汰缓存,数据库更新成功,但是缓存更新失败的话,缓存是旧数据,要等下一次更新或者写操作,这样会引发数据不一致

假设先淘汰缓存,再写数据库,淘汰缓存成功,写数据库失败的话,则会引发一次Cache Miss

故:

数据库和缓存,必须先进行淘汰缓存,再写数据库

2. 怎么产生的脏数据

2. 1 单库下

下面对上面的四个步骤进行说明:

  1. 左边的请求A发起一个写操作,先进行第一步,淘汰Cache,然后到服务层进行处理,这里出现某些原因而卡住了,假设进行了1秒钟,上图步骤1
  2. 右边的请求B发起了一个读操作,读取Cache,Cache Miss,上图步骤2
  3. 请求B去读DB,读出一个脏数据,将脏数据写入Cache,上图步骤3
  4. 请求A现在再去写DB,写入最新的数据,上图步骤4

上面请求B就请求到了脏数据,请求B在请求A中间完成了。

2.2 主从同步,读写分离

  1. 请求A发起一个写操作,第一步淘汰Cache,上图步骤1

  2. 请求A写入数据库,写入最新数据,上图步骤2

  3. 请求B发起一个读操作,读Cache,Cache Miss,上图步骤3

  4. 请求B读取从库,此时主从同步还未完成,读取了一个脏数据放入Cache,上图步骤4

  5. 数据库的主从同步完成(假设也花了1秒),上图步骤5

这个时候主要原因是因为读从库读到了旧数据,请求B在主从同步中间完成了。

2.3 解决方案

我们假设的是因为1秒的时差导致了获取到的是脏数据,那我们可以在写数据库的1秒之后再次淘汰缓存吗?

public void write(String key,Object data){
        redis.delKey(key);
        db.updateData(data);
        Thread.sleep(1000);
        redis.delKey(key);
    }

答案是可以的,但是这样每一次的写操作都增加这个步骤,势必会导致性能下降。

其实我们第二次主要是为了保证缓存的一致性,那么我们可以采用一个异步的Timer来进行更新缓存。还有其他的比如利用消息总线或者mysql的binlog等来实现,这里就提一下,以后再看看吧

上面的就是我们说的双淘汰策略。

转载来自:主从DB与cache一致性

  • 4 min read

CONTRIBUTORS


  • 4 min read