步骤1:线程1更新MySQL,库存=100

步骤2:线程1更新Redis,库存=100

步骤3:线程2更新MySQL,库存=90

步骤4:线程2更新MySQL,库存=90

有并发线程1和2,他们两都需要更新库存,如果此时命令执行顺序按照预期行为走了,好像没什么问题。

 

但是,如果在远程调用过程中,网络出了问题(抖动、超时、阻塞等)

步骤1:线程1更新MySQL,库存=100

步骤2:线程2更新MySQL,库存=90

步骤3:线程2更新Redis,库存=90

步骤4:线程1更新MySQL,库存=100

发现问题所在了吧,数据不符合基本逻辑了,这时候在Redis获取的数据是错的,和数据库的信息不一致(数据库中是90,而Redis中是100)。

当操作序列没有严格按照请求的先后顺序执行时,会引发并发安全问题。

 

那我们需要通过以下几种常见的方式来解决数据一致性的问题

1.采用延时双删策略+设置缓存过期时间

在写库前后都进行redis.del(key)(或更新缓存)操作,并且设定合理的超时时间(比如500ms)。因为设置了超时时间,即使有脏数据也是在超时时间内,缓存过期后,最终数据还是会从数据库获取最新的,这样就可以保持数据一致性

至于这个超时时间设置多久,就要看处理业务的耗时情况,这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。

 

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

这样最差的情况就是在超时时间内数据存在不一致,而且又增加了写请求的耗时。

 

2.通过Canal订阅binlog ,再利用消息队列,推送更新redis缓存数据

canal是阿里的一款开源框架,通过该框架可以对MySQL的binlog进行订阅(类似MySQL主备同步的方式),而 canal 正是模仿了 mysql 的slave 数据库的备份请求,使得 Redis 的数据更新达到了相同的效果

(.Net Core 版本的canal插件 https://github.com/dotnetcore/CanalSharp)