Redis缓存和数据库的数据一致性问题

222 阅读6分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第5篇笔记

Redis缓存和数据库的数据一致性问题

Redis数据保存在内存中,为了提高效率,我们常会想用redis来做缓存,而在实际运用Redis做缓存时,我们常会遇到一些异常问题,主要就是4个方面:缓存中的数据和数据库中的不一致;缓存雪崩;缓存击穿和缓存穿透。

这一篇文章主要聊聊缓存和数据库间的一致性保证问题。

缓存和数据库的数据不一致是如何发送的?

“一致性”,根据redis中有没有缓存可分为两种情况:

  • 有缓存,那此时缓存中的数据必须和数据库中的数据相同,此时是从缓存中读取数据
  • 没有缓存,此时就得保证数据库中的数据是最新的数据,此时是从数据库中读取数据。

不过,当缓存的读写模式不同时,缓存数据不一致的发生情况也会不一样,我们的应对方法也有所不同,所以下面根据缓存的读写模式,分别了解不同模式下的缓存不一致情况。

读写缓存:

对于读写缓存,我们在修改数据时,就得在缓存中进行修改,而修改后选择的同步策略就会带来一致性问题

  • 同步直写策略:写缓存时,也同步写数据库,在业务应用要要使用事务机制,使更新具有原子性,要么更新缓存和更新数据库都成功,要么都不成功。
  • 异步写回策略:写缓存时不同步写数据,等到数据从缓存中淘汰时,再写回到数据库,而这里又有个大问题,就是如果数据还没有写回数据库的时候,缓存发生了故障,那数据库就不会得到最新的消息了

只读缓存:

只读缓存,顾名思义,只提供读的功能,当我们进行增删改操作时,该类操作会直接在数据库中进行,并且给已经存在于缓存中的数据打上失效的标签,等到后面有新的请求进来要读取这些数据时,就会到数据库中读取最新的数据到缓存中,接着后面的操作就都可以从缓存中读取最新数据了。

下面我们把增操作和删操作分开讨论在他们身上会发生的数据不一致的情况:

  • 新增数据:新增数据一般是不会出现一致性问题的,正如上面的“一致性”的第二种情况,此时我们插入数据时只有对数据库有操作,且将redis中的数据标记为无效,也就是缓存中无数据,数据库中数据是最新的的情况
  • 删改数据:此时就会麻烦许多,涉及到对redis的修改和数据库的修改两个操作,如果两个操作不能保证原子性,那就会出现一致性问题
    • 先删缓存,再删数据库:如果删除缓存成功后,删除数据库信息失败,那此时后面的请求进来后,就会发现缓存中的数据已经失效,那就会到数据库中读取,而此时数据库的数据还是旧数据,那么之后后面所有的请求都会读到这个旧数据。
    • 先更新数据库,再删除缓存:这更不用说了,如果数据库删除成功了,缓存没删掉,下一个请求进来,缓存还有效,直接就把旧数据读走了。

解决一致性问题:

重试机制:

利用重试机制,也就是将要删除的缓存值或者是要更新的数据库值暂存到消息对列中。当应用没有能够成功地删除缓存值或者是更新数据库值时,可以从消息对列中重新获取这些值,再进行删除或更新,当然若是本来就删除成功了,那就直接将这些值从消息对列中删除即可。

高并发场景:

上面说的是删改操作时,删除缓存值或者是删除数据库值其中一步失败解决的方案,但如果在遇到很强的高并发时,还是会出现数据不一致的情况。我们分两种情况看。

  • 先删除缓存,再更新数据库:假设有两个线程,A线程执行了删除缓存操作后,由于网络原因还是其它不可抗因素导致更新数据库操作迟迟没有完成,这是B线程进来了,它首先从缓存中读数据,发现数据失效,于是就到数据库中读数据,于是它就将旧的数据读到了缓存上,而不仅它自己读到了旧数据,它后面接下来的请求都会读到旧数据,后面A线程终于更新完了数据库,那此时就出现缓存数据(旧)和数据库数据(新)的不一致问题了。

    • 解决方案:“延迟双删”,在线程A更新完缓存,更新完数据库后,我们让它Sleep一下,再进行一次缓存的删除操作,之所以要加上Sleep的这段时间,就是为了保证最坏情况下,线程B已经将旧数据读到了缓存中,也就是这个时间得大于线程B读取数据再把它存到缓存中的时间,这样我们就能够保证将这个可能的旧的缓存信息给删除。
  • 先更新数据库,再更新缓存值:如果A线程在删除完数据库后,成功更新缓存的值之前线程B进来了,这是它一读,发现缓存有效,那就直接把旧数据读走了,不过,这种情况下,如果并发读缓存的请求不多,那么,就不会有很多请求读到旧值,而且,线程A一般很快就能删除缓存值,这样的话,其实这种情况对业务的影响很小。

总结:对不保证两步操作都有成功进行,我们采用利用消息对列的重试机制,对于高并发场景下的先删除缓存,再更新数据库的情况,我们可以采用“延迟双删”的方案,先更新数据库,再删除缓存的情况可以在一些特定的业务场景下使用,即使出现问题也影响不大。

引用:开篇词 | 这样学Redis,才能技高一筹 (geekbang.org)