多线程高并发下数据一致性问题
缓存一致性背景
在高并发的场景下,缓存往往成为缓解数据库I/O压力最完美的选择;将并发高峰期请求频繁的数据放入缓存,请求派发时优先操作(读取或者修改)缓存中的数据,然后在异步同步到数据库中。这种处理方式在理想情况下是很完美的,它使得数据库I/O不再试并发瓶颈,指数扩张了整个系统的吞吐量。倒是现实却也存在很多问题:

数据一致性问题,是指缓存中的数据和持久化存储中的数在某一时刻存在不一致的情况。今天缓存以redis为例,持久化以mysql为例。
解决方案
不管是先写库还是先删除数据在写库,还是先删除数据然后写库然后再删除,都存在数据不一致的情况,因为在高并发的场景下,写和读是高频率并发的,你无法提前预知谁先到来。针对这一问题,目前业界有一下四个解决方案:
第一方案:经典的延时双删策略,伪代码如下:
public void updateUserInfo(String key,UserBasic userBasic){redis.delKey(userBasic.getUserid());userBasicMapper.update(userBasic);Thread.sleep(200);redis.delKey(userBasic.getUserid())}
这种方式,在一定程度上能够解决数据一致问题,在数据库I/O写入完成之后再次清理缓存,能够让最新的数据以最短的时间同步的缓存中去,在写数据库期间的读请求还是会打到数据中中,存在两个明显的问题:1,200毫秒怎么来的,主要考虑数据库主从同步时间,或者直接从主库读取缓存数据;2,清空缓存之后,高并发操作mysql存在缓存击穿的风险。
第二方案:基于binlog的异步消息队列同步缓存机制。该方法要求通过mysql数据库binlog伪代实时更新新缓存,这里有两个要求:1,实时监控binlog,对biglog中的add,update,del操作进行处理;2,通过消息队列异步更新缓存。该方案也存在以下问题:1,需要等待数据库主从完成之后才能进行缓存同步,这期间是一段空档期,缓存和数据库中数据是不一致的。2,全量更新数据流可能较大,增量更新对于数据丢失问题比较难处理。
第三方案:删除缓存更新主库成功之后,更新缓存,设置过期时间约为数据库主从时间;然后再推迟指定时间异步更新缓存。这种处理方式能够解决延时双删的缓存击穿问题,能够最大限度的满足数据一致性问题。伪代码如下:
public void updateUserInfo(String key,UserBasic userBasic){redis.del(userBasic.getUserid());boolean success = userBasicMapper.update(data);if(success){redis.put("newdata",userBasic,200);}new Thread(new Runnable() {@Overridepublic void run() {Thread.sleep(200);redis.delKey(userBasic.getUserid())}})}
第四方案:不删除缓存,数据库主从设置同步后返回,然后更新缓存或者直接读主库。这个我就不写伪代码了。大家理解就好,没理解透也没关系私信我。
建议
对于以上数据一致性解决方案,第四方案是最优选,在数据库主库操作没返回前,不清理缓存,以为这个时候数据库数据还没更新,和缓存的旧数据是一致的。主库更新完成之后,暂时使用操作数据更新缓存,确保了缓存数据和主库数据一致。在主从同步之后再异步更新缓存,确保了缓存和更新了数据之后的从库数据一致。比较完美的解决了以上问题。
喜欢的同学欢迎点赞、关注、转发三连!





