保持数据库和redis的数据一致性大致有两种方案:
一:先删除缓存,再更新数据库
该方案会导致不一致的原因是。同时有一个请求A进行更新操作,另一个请求B进行查询操作。那么会出现如下情形:
- 1)请求A进行写操作,删除缓存
- 2)请求B查询发现缓存不存在
- 3)请求B去数据库查询得到旧值
- 4)请求B将旧值写入缓存
- 5)请求A将新值写入数据库
上述情况就会导致不一致的情形出现。而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。
解决方案:采用延时双删策略
二:先更新数据库,再删除缓存
假设这会有两个请求,一个请求A做查询操作,一个请求B做更新操作,那么会有如下情形产生
- 1)缓存刚好失效
- 2)请求A查询数据库,得一个旧值
- 3)请求B将新值写入数据库
- 4)请求B删除缓存
- 5)请求A将查到的旧值写入缓存
ok,如果发生上述情况,确实是会发生脏数据
发生上述情况有一个先天性条件,就是步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。可是,大家想想,数据库的读操作的速度远快于写操作的(不然做读写分离干嘛,做读写分离的意义就是因为读操作比较快,耗资源少),因此步骤(3)耗时比步骤(2)更短,这一情形很难出现。
三:最佳解决方案:
方案一
先做一个说明,从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。这种方案下,我们可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。
方案二
Redis里的数据总是不过期,但是有个背景更新任务(“定时执行的代码” 或者 “被队列驱动的代码)读取db,把最新的数据塞给Redis。这种做法将Redis看作是“存储”。访问者不知道背后的实际数据源,只知道Redis是唯一可以取的数据的地方。当实际数据源更新时,背景更新任务来将数据更新到Redis。这时还是会存在Redis和实际数据源不一致的问题。如果是定时任务,最长的不一致时长就是更新任务的执行间隔;如果是用类似于队列的方式来更新,那么不一致时间取决于队列产生和消费的延迟。常用的队列(或等价物)有Redis(怎么还是Redis),Kafka,AMQ,RMQ,binglog,log文件,阿里的canal等。
注意,没有所谓的最佳解决方案,针对不同的业务场景我们自主选择不同的方案,不大可能达到十全十美