双写一致性问题

1、一致性问题

双写一致性问题_第1张图片

举个例子来说,现在我们在数据库中进行值减一,1000变为999了,但是在某一刻的时间里,可能缓存还没有进行更新,还是1000,在这种情况之下,会出现这两个地方数据不一致,所以为了保证双方数据一致,从而出现了双写一致性的问题。

分布式缓存是现在很多分布式应用中必不可少的组件,但是用到了分布式缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?


2、解决方案

1、Cache Aside Pattern

最经典的缓存+数据库读写的模式,就是 Cache Aside Pattern。 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。 更新的时候,先更新数据库,然后再删除缓存。 为什么是删除缓存,而不是更新缓存? 原因很简单,很多时候,在复杂点的缓存场景,缓存不单单是数据库中直接取出来的值。 比如可能更新了某个表的一个字段,然后其对应的缓存,是需要查询另外两个表的数据并进行运算,才能计算出缓存最新的值的。 另外更新缓存的代价有时候是很高的。 是不是说,每次修改数据库的时候,都一定要将其对应的缓存更新一份? 也许有的场景是这样,但是对于比较复杂的缓存数据计算的场景,就不是这样了。 如果你频繁修改一个缓存涉及的多个表,缓存也频繁更新。 但是问题在于,这个缓存到底会不会被频繁访问到? 举个栗子,一个缓存涉及的表的字段,在 1 分钟内就修改了 20 次,或者是 100 次,那么缓存更新 20 次、100 次; 但是这个缓存在 1 分钟内只被读取了 1 次,有大量的冷数据。 实际上,如果你只是删除缓存的话,那么在 1 分钟内,这个缓存不过就重新计算一次而已,开销大幅度降低,用到缓存才去算缓存。 其实删除缓存,而不是更新缓存,就是一个 lazy 计算的思想,不要每次都重新做复杂的计算,不管它会不会用到,而是让它到需要被使用的时候再重新计算。 像 mybatis,hibernate,都有懒加载思想。 查询一个部门,部门带了一个员工的 list,没有必要说每次查询部门,都里面的 1000 个员工的数据也同时查出来啊。 80% 的情况,查这个部门,就只是要访问这个部门的信息就可以了。 先查部门,同时要访问里面的员工,那么这个时候只有在你要访问里面的员工的时候,才会去数据库里面查询 1000 个员工。

【翻译】Cache Aside Pattern就是缓存 + 数据库读写的模式

的时候先读缓存,缓存没有的情况之下,再去读数据库,然后取出数据放入缓存,同时返回响应

更新的时候先去更新数据库,然后把原有的缓存直接删除掉,再把更新的数据放入缓存之中

为什么是删除缓存后创建一个新缓存去存取数据库中更新的数据,而不是直接更新缓存呢,因为如果更新缓存的话,因为需要计算出更新的数据在缓存中的哪里,这样计算浪费时间,影响效率,莫不如直接删除,创建一个新的,

【问题】因为是先访问数据库后访问缓存,某一刻数据库更改成功但是缓存更新失败,这样的情况下读的时候因为先读缓存所以还会出现读写不一致的情况

【解决方案】更新的时候先删除缓存 再修改数据库 

   这样的话,如果数据库修改失败,数据库中依旧是旧数据,因为先删除缓存了,所以缓存中是空的,直接去数据库读不会出现读写不一致,然后读完了再放进缓存中

这样的话两次删除缓存的内容一样,但是解决了读写一致问题

2、比较复杂的数据不一致问题分析

我们读取数据库中的数据,然后再放进缓存里,这个是读的请求来做的,

上亿流量高并发场景下,缓存会出现一个问题,只有在对一个数据在并发的进行读写的时候,才可能会出现这种问题。 其实如果说你的并发量很低的话,特别是读并发很低,每天访问量就 1 万次,那么很少的情况下,会出现刚才描述的那种不一致的场景。 但是问题是,如果每天的是上亿的流量,每秒并发读是几万,每秒只要有数据更新的请求,就可能会出现上述的数据库+缓存不一致的情况

也就是说,在读多写少高并发的情况

如果说现在修改的请求开始进行,先删除缓存之后,然后开始更改数据库,再更改之前,这个时候另一个读的请求过来了,发现缓存没有,先读取了将要更改但是还没有更改的数据库,然后放进了缓存里,然后进行修改的请求再进行更改数据库,这种情况之下,又会造成了读写不一致的问题

发生了脏读的情况

【解决方案】先将要修改的数据放进mq消息队列里边,然后同时生成两个消费者,一个进行修改数据库,一个进行修改修改redis 将两者进行原子操作,增加分布式事务,要么都成功,要么都失败,保证读写一致性

数据库和redis之间是如何数据同步的

双写一致性问题_第2张图片

 

 

你可能感兴趣的:(java,缓存,分布式)