缓存与数据库一致性

使用redis一般是看重它支持的并发量较关系性数据库要大,还有一点就是扩容相对容易,至于在数据上与数据库保证强一致性是很难的。对于CAP原则来说,redis属于AP.

当然我们也要尽量朝着一致性的方向去努力。

一般的业务场景中,读操作会先读取缓存,缓存没有,去数据库加载数据到缓存然后返回。想要避免多个线程同时去加载数据给数据库造成压力,可以设置一个锁,只有获取到锁的线程才能去访问数据,其它未获取到锁的线程可以sleep一小段时间然后直接去读缓存。

如果数据要进行修改,那大概会是:先更新缓存,再更新数据库;先更新数据库,再更新缓存两种情况。

原子操作问题

很显然这两种操作里的两个更新并不是原子操作,会有第一步成功第二步失败的可能,既然同步不行,第二步的更新使用消息中间件呢?这个是没问题的,借助消息中间件可以做到数据的最终一致性,不过缺点也是很明显:异步较于同步,数据库与redis数据不一致的时间更长,当然业务允许倒也没事。

如果在并发场景下分别是什么样的呢?

并发问题

还是分别来看:

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

1)A线程更新缓存成功将缓存值设为A,但还没更新数据库

2)B线程更新缓存成功将缓存值设为B,且早于A线程将数据库里的值也更新为B

3)A线程将数据库里值更新为A

最终的结果是缓存数据为最新的值B,数据库数据为旧A,从更新的时间先后来说缓存与数据库数据都为B才对。

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

1)A线程更新数据成功将值设为A,但还没更新缓存

2)B线程更新数据成功将值设为B,且早于A线程将缓存里的值更新为B

3)A线程更新缓存的值为A

最终结果是数据库值为最新的值B,缓存为旧值A,同样,从更新的时间先后来说缓存与数据库数据都为B才对。

不管哪种情况数据库里面的值肯定是要更新的,但是数据库的值更新了不一定会马上使用,所以立即更新缓存可能会造成缓存空间的浪费。那缓存里的值不进行更新而是删除又会是什么情况呢?同样有两种:

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

1)A线程删除缓存成功,还没来得及更新数据库

2)B线程去读缓存发现缓存失效,去数据库读取了旧值X

3)A线程更新数据库值为A

4)B线程将旧值X写入缓存

最终结果是数据库为新值A,缓存仍然为旧值X。

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

1)缓存失效,A线程读取数据库旧值X

2)B线程更新载数据库值为B

3)B线程删除缓存成功

4)A线程写入旧值X到缓存

最终结果是数据库为新值B,缓存仍然为旧值X。

虽然第3与第4在并发场景下仍然存在不一致的情况,但两者发生的概率还是有差异的,我们先看第4种先更新数据库再删缓存出现不一致的条件:

1)缓存失效

2)读+写并发

3)B线程更新数据库+删除缓存时间比A线程读取数据库+写入缓存短

更新数据库时一般会加锁而读不会,理论上更新数据库+删除缓存要比读数据库+写缓存时间长一些,所以这第3)点出现的可能会很小。而使用先删缓存再更新数据库出现读线程将旧值再读入缓存的可能性非常大,所以再综合缓存空间的利用率还是建议先更新数据库再删除缓存。

当然更新数据库成功后接着再删除缓存也是会有失败的可能性的,这个时间就需要考虑重试,但如果要保证最终删成功,可以考虑错助消息中间件。

 

最后总结下:

1,redis只能做到AP原则

2,尽量追求一致性建议先更新数据库再删缓存,引入消息中间件做到最终一致性

3,选用哪种方案要结合自身业务

 

posted @ 2021-12-16 10:21  weilu  阅读(58)  评论(0)    收藏  举报