一,什么情况下使用双写?
在电商系统中,一部分数据是要实时显示给用户的,例如:商品的价格,商品的库存等。
在交易系统中,用户委托数量,成交量等。
以上这些数据变更后需要第一时间显示给用户,但并发量又相当高。这时我们就需要将数据进行双写(数据库写,redis写)。
双写常见的有以下两种策略:
一.先删除缓存再更新数据库
二.先更新数据库再删除缓存
注:数据库数据变更后更新缓存数据,这种策略基本上没人用。很多情况下,更新缓存数据的成本并不简单,所以没必要在数据变更的时候就去更新缓存,而是在读取的发现缓存为空再更新数据到缓存。想想赖汉式就明白啦。
1.先删除缓存再更新数据库,双写不一致的情况:
(1)线程A对数据 id=1 删除缓存,修改数据
(2)线程B读取id = 1的数据,发现缓存中没有
(3)线程B 去数据库获取id=1的数据(这时线程A还未将修改数据commit)放入缓存。
(4)线程A提交数据库事物。
这时缓存中的数据和数据库的数据不一致。
2.先更新数据库再删除缓存,双写不一致的情况:
(1) 线程A修改id=1数据,提交事物。
(2)线程A删除缓存中id=1的数据(失败了)
这时数据库和缓存的数不一致。如果删除缓存在事物中,则不会出现这种情况,因为删除缓存报错事物会被回滚。
3. 双写不一致的情况三:
(1)线程A去缓存读取id=1的的数据,发现没有
(2)线程A去数据库获取id=1的数据。
(3)线程B此时正好修改id=1的数据。
(4)线程B删除缓存中的数据。
注:(3)(4)反过来也一样
(5)线程A将从数据库获取的老数据放入缓存。
这时数据库和缓存中的数据又出现不一致的情况。
如何解决上面的问题呢?
1.缓存设置过期时间
这种方式最为简单,等缓存到达过期时间后自动清除,保证最终一致性的要求。
2.串行化操作
(修改数据库数据+删除缓存)和(获取数据库数据+放入缓存) 变为两个原子化且串行化操作。
之所以出现上面的情况主要原因就是并发操作缓存。如果我们将缓存操作进行串行化就解决的这个问题。
如果全局串行化务必会造成效率低下的情况。
解决上面的问题我们可以在jvm创建多个队列,每一个队列由一个固定线程去消费更新缓存。将要被更新数据的id值对队列数进行
取余路由到相应的队列中。如图:
3.二次淘汰缓存
1.先进行缓存清除。
2.更新数据库数据
3.延迟一定时间(根据情况来确实延迟时间)再进行缓存清理。
如图: