如果在高并发修改的场景,会存在redis和MySQL数据不同步的问题。
比如,修改某个商品的价格, 第一种情况: 可以先把缓存删掉,然后修改MySQL商品价格。然鹅,MySQL商品价格还没来得及修改,另外一个读商品的请求过来了,redis没读到,就会读MySQL的老数据,并加载到redis。过了一会,第一个请求把MySQL的商品价格修改成功了,就会导致两边数据不一致的情况。 第二种情况: 先修改MySQL商品价格,再删缓存,MySQL价格修改了,缓存删除失败了,就会导致不一致的情况。
解决方案:
- 延时双删:delete key -> update MySql -> thread.sleep(1s) -> delete key
- 设置过期时间 -> update MySql -> delete key
- binlog同步到redis
- 加分布式锁
- 事务框架处理
对方案1的理解: 请求1先删除key,另外一个读请求,2进来了,发现没有缓存,就去从MySQL获取老数据,并加载到redis,这时请求1把MySQL更新了,然后再次删除缓存。为什么还要加个延时呢,因为存在这样一种情况,请求2去加载缓存之前,请求1已经删除了缓存,所以要等待至少完成一次读的时间,同时也得考虑redis和数据库主从同步(读从从库读)的耗时,一般几百ms,等待这两个时间之和再完成key的delete。
对方案2的理解: 这个就好说了,先把这个key设置个过期时间,MySQL修改完之后,再去删除key,如果删除失败了,有前面过期时间的保证。如果MySQL修改失败了,过期之后,再重新加载MySQL也么问题。弊端就是,如果最后一步删除失败了,可能会存在短时间的不一致。
对方案3的理解: 保证最终一致性,修改MySQL完之后,同步binlog到redis,相当于把redis当作了slave来对待。其实也可以把方案3当作前两种方案的兜底方案,用mq做串行化(顺序)后,去删除。
对方案4的理解: 加分布式锁,对修改行为做串行化。当请求2发现redis没有缓存时,去加载老的MySQL记录到缓存,发现请求1已经持有对这条记录锁了,请求2就会放弃本次操作。分布式锁,会有一定并发量的损失。
针对不同场景用不同方案: 修改帖子/评论:这个用方案2,方案3,4都可以,修改后的数据,有一定延时,对用户来说没多大影响 修改商品属性:结合1,2,3,4都可以
还有什么场景,可以拿来讨论的,可以在评论区讨论
|