摘要:使用Redis缓存中间件做数据缓存,对缓存数据进行修改,并且与数据库进行交互过程中,必然存在缓存和数据库一致性问题。若两边的数据不一致,则请求从Redis缓存中间件读取的数据就不是最新的数据,引发业务层面的一些问题;如网上商城库存数量缓存在Redis当中,若出现库存数据不对,则会导致下单后一系列问题,这是非常严重的。
1、什么是Redis缓存与数据库一致性
若Redis缓存存有数据,就需要缓存的数据值和数据库保持一致;若Redis缓存没有保存数据,则数据库是最新值。当这两种情况都不符合,就会发生Redis缓存与数据库不一致性问题。
2、Redis缓存数据的增删改
2.1、新增数据
将数据直接写到数据库,不对Redis缓存进行新增,此时,Redis缓存没有新增数据,而数据库是最新值;
或者写到数据库的同时写到Redis缓存,此时缓存和数据库中的数据也是一致的,所以Redis缓存与数据库是一致性的。
2.2、删除和修改数据
既要更新缓存数据,也要更新数据库,此时无法进行原子性操作,就会存在Redis缓存与数据库不一致性问题。
2.2.1、先删除缓存,再更新数据库
首先应用删除redis数据,此时刚好有请求读取数据库中数据,读取到旧值,更新到redis,最后才更新数据库;就会发生缓存和数据库的数据一致性问题。
2.2.2、先更新数据库,再删除缓存
首先更新数据库数据,更新成功后,去删除redis数据,若出现redis数据删除失败,请求到达仍是读取到redis的旧值,同样也会发生缓存和数据库的数据一致性问题。
3、删除redis缓存数据情况(一致性解决方案)
3.1、无并发
将要删除缓存的数据、要更新数据库的数据推送到消息中间件mq,消息消费成功后,保证redis缓存和数据库中数据的一致性;若消费失败,则保存在消息中间件mq当中,采用重试机制进行消费,若出现消费失败次数超过一定次数,则向业务层进行报错信息输出,同时发送告警信息,进行手动处理。
3.2、高并发(分情况提出解决方案)
3.2.1、先删除缓存,再更新数据库
采用延迟双删策略,首先应用删除redis缓存数据,此时刚好有请求读取数据库中数据,读取到旧值,更新到redis,这个时候redis缓存保存的是旧数据,最后才更新数据库,更新数据成功后,通过睡眠N毫秒(睡眠时间确保大于请求读取数据库中数据并且缓存到redis的时间),再次对redis缓存数据进行确保删除,称为“延迟双删”;
3.2.2、先更新数据库,再删除缓存
这种情况下,在reids数据删除成功的情况下,其实线程读取到错误数据的请求并不多,对业务影响较小。
总结:缓存数据和数据库不一致的原因也都有了对应解决方案。
- 删除redis缓存或更新数据库失败而导致数据不一致的情况下,采用重试机制,确保删除或更新成功;
- 在删除redis缓存、更新数据库这两步操作中,有其他线程的并发读操作,导致其他线程读取到旧值,采用延迟双删策略。
4、更新redis缓存数据情况(一致性解决方案)
4.1、无并发
4.1.1、先更新redis缓存,再更新数据库
若redis缓存更新成功,但是数据库更新失败,此时其他线程请求获取到是最新值,但是数据库是旧值。后续读请求会直接命中缓存,但得到的是最新值,短期对业务影响不大。但一旦缓存过期或满容后被淘汰,读请求就会从数据库中重新加载旧值到缓存中,之后的读请求会从缓存中得到旧值,对业务产生影响。
4.1.2、先更新数据库,再更新redis缓存
若更新数据库成功,但是更新redis更新失败,此时数据库中数据是最新值,但是redis是旧值,后续请求会直接命中缓存,得到是旧值;
针对上述情况,仍然可以采用重试机制进行更新,以此来达到redis缓存和数据库一致性。
4.2、高并发(分为以下四种情况)
4.2.1、写+读并发
4.2.1.1、先更新数据库,再更新redis缓存
线程A首先更新数据库,后有线程B读取缓存数据,此时线程B会读取到旧值,之后线程A会更新缓存成功,后续的请求会读取到新值,对业务短暂无影响。
4.2.1.2、先更新redis缓存,再更新数据库
线程A首先更新redis缓存,后有线程B读取缓存数据,此时线程B会读取到新值,之后线程A更新数据库成功。这种场景下,虽然线程A还未更新完数据库,数据库会与缓存存在短暂不一致,但在这之前进来的读请求都能直接命中缓存,获取到最新值,所以对业务没影响。
4.2.2、写+写并发
4.2.2.1、先更新数据库,再更新redis缓存
线程A和线程B同时更新数据库同一条数据,更新数据库的顺序是先A后B,但更新缓存时顺序是先B后A,这会导致数据库和缓存的不一致。
4.2.2.2、先更新redis缓存,再更新数据库
线程A和线程B同时更新缓存同一条数据,更新缓存的顺序是先A后B,但更新数据库时顺序是先B后A,这会导致数据库和缓存的不一致。
针对写+写并发,配合分布式锁。针对同一资源的修改操作,先加分布式锁,这样同一时间只允许一个线程去更新数据库和redis缓存,没有拿到锁的线程把操作放入到消息中间件MQ,延时处理。 这样保证多个线程操作同一资源的顺序性,以此保证一致性。