缓存和主存数据不一致的问题,其最基本的原理就是,和造成的结果就是数据的写和读是并发的。
举例
在分布式环境下,数据的读写都是并发的,上游有多个应用,通过一个服务的多个部署(为了保证可用性,一定是部署多份的),对同一个数据进行读写,在数据库层面并发的读写并不能保证完成顺序,也就是说后发出的读请求很可能先完成(读出脏数据):
a)发生了写请求A,A的第一步淘汰了cache(如上图中的1)
b)A的第二步写数据库,发出修改请求(如上图中的2)
c)发生了读请求B,B的第一步读取cache,发现cache中是空的(如上图中的步骤3)
d)B的第二步读取数据库,发出读取请求,此时A的第二步写数据还没完成,读出了一个脏数据放入cache(如上图中的步骤4)
即在数据库层面,后发出的请求4比先发出的请求2先完成了,读出了脏数据,脏数据又入了缓存,缓存与数据库中的数据不一致出现了
读数据操作流程基本如下,一般不会出现问题,因为没有涉及到数据的修改更新
缓存和数据库一致性解决方案
第一种方案:采用延时双删策略
在写库前后都进行redis.del(key)操作,并且设定合理的超时时间。
伪代码如下
public void write(String key,Object data){
redis.delKey(key);
db.updateData(data);
Thread.sleep(500);
redis.delKey(key);
}
2.具体的步骤就是:
1)先删除缓存
2)再写数据库
3)休眠500毫秒
4)再次删除缓存
那么,这个500毫秒怎么确定的,具体该休眠多久呢?
需要评估自己的项目的读数据业务逻辑的耗时。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。
当然这种策略还要考虑redis和数据库主从同步的耗时。最后的的写数据的休眠时间:则在读数据业务逻辑的耗时基础上,加几百ms即可。比如:休眠1秒。
3.设置缓存过期时间
从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。所有的写操作以数据库为准,只要到达缓存过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。
4.该方案的弊端
结合双删策略+缓存超时设置,这样最差的情况就是在超时时间内数据存在不一致,而且又增加了写请求的耗时。
第一种方案:采用延时双删策略
使用消息队列保证在执行同一数据的时候,进行串行操作。
消息队列接受任务是串行,但是在执行任务是并行。在数据库连接的时候也是并行,那么只要保证操作同一数据的操作是串行就可以啦,
修改数据库DB连接池,同一数据id取模选取DB连接,能够保证同一个数据的读写在数据库层面是串行的