延迟双删策略是什么?
延迟双删策略是一种保证缓存与数据库数据一致性的方法,特别适用于高并发场景下的缓存更新。其核心思想是:在更新数据库时,不仅删除一次缓存,还在短时间后再进行一次延迟删除,以避免并发问题导致的数据不一致。
延迟双删策略的步骤
- 第一次删除缓存:在更新数据库之前,先删除对应的缓存,确保缓存中不会存在旧数据。
- 更新数据库:执行数据库更新操作。
- 延迟一段时间后,再次删除缓存:为了防止在数据库更新后立即有并发请求读到了旧缓存数据,延迟一定时间(比如 1 秒)后,再次删除缓存,确保缓存中不存在陈旧的数据。
为什么需要延迟再删一次缓存?
在高并发的场景下,延迟双删策略可以有效避免以下两种常见的问题:
- 更新数据库后立即有请求查询:当某个线程刚更新完数据库,在其还未重新更新缓存前,有其他线程同时查询到了旧缓存数据。这种情况会导致数据库中的新数据还没被缓存,查询到的却是旧数据。
- 脏数据回填缓存:由于并发请求的存在,数据库更新后有可能缓存还没被删除或者重建,导致旧缓存被重新写入。
延迟双删策略的优点
- 提高数据一致性:通过延迟删除可以减少缓存中的陈旧数据,避免因并发请求导致的缓存数据与数据库数据不一致的问题。
- 减少缓存更新压力:相比于每次数据库更新都立即更新缓存,延迟双删避免了频繁的缓存更新请求,降低了缓存服务器的压力。
- 简单易实现:不需要对现有的缓存系统做太大的修改,只需在更新逻辑中加入两次删除操作,成本较低。
延迟双删策略的缺点
- 延迟时间的难以确定:延迟多久再删除缓存是一个难点。如果延迟时间太短,可能仍然会出现并发问题;如果延迟时间太长,则可能增加缓存中的陈旧数据存在的时间,影响数据一致性。
- 并发高时可能出现不一致:即使有延迟双删,极端高并发场景下(如延迟期间有大量请求访问),仍然可能出现数据不一致的情况。比如在延迟时间内,有大量读请求仍然会命中旧缓存。
- 多次缓存删除带来额外开销:延迟删除缓存意味着每次数据库更新后都要进行两次缓存删除操作,虽然可以保证数据一致性,但也可能带来额外的性能开销,特别是在高并发场景下,缓存的删除和重建会增加负载。
延迟双删策略适用场景
延迟双删策略适合于那些需要在高并发环境下保证缓存与数据库一致性的场景,特别是在更新操作频繁的系统中,比如电商、社交平台等需要对大量缓存数据进行频繁更新的应用场景。
示例
假设我们有一个电商系统,当用户下单时,库存数据需要更新。延迟双删的实现大致如下:
// 更新库存的伪代码
public void updateInventory(String productId, int newInventory) {// 第一次删除缓存cache.delete(productId);// 更新数据库中的库存信息database.updateInventory(productId, newInventory);// 延迟一段时间再删除缓存try {Thread.sleep(1000); // 延迟1秒} catch (InterruptedException e) {e.printStackTrace();}// 再次删除缓存,确保数据一致性cache.delete(productId);
}
基础总结一下但是任然有问题:
延迟双删策略通过在数据库更新前和更新后延迟一段时间再次删除缓存,有效避免了在并发情况下缓存和数据库数据不一致的问题。虽然它能提高一致性,但需要合理设计延迟时间,并且在极高并发的场景下仍需考虑其他优化策略。
延迟双删策略无法完全做到强一致性!
延迟双删策略虽然能在一定程度上提高缓存与数据库的一致性,但它确实无法做到完全一致性,特别是在极高并发的情况下。下面是对这一点的详细分析:
为什么延迟双删策略不能做到彻底的一致性?
-
延迟时间难以完美把控:
- 延迟时间的设定是策略的核心,但它往往很难确定。设定的时间过短,可能无法覆盖到并发操作的峰值,从而导致旧缓存数据被读取。设定时间过长,则会影响系统性能和实时性。对于不同业务场景和流量模式,理想的延迟时间往往是动态变化的,这使得延迟双删的应用更具挑战性。
-
高并发场景下的并发写入问题:
- 在极高并发的场景下,即使使用延迟双删策略,仍可能出现多个线程同时操作缓存和数据库的情况。举个例子:
- 线程 A 在删除缓存后,开始更新数据库;
- 线程 B 在 A 更新数据库之前,查询到了缓存并回填了旧缓存数据;
- 线程 A 的延迟删除动作也无法及时清理掉线程 B 填充的旧缓存。 这种情况下,会导致缓存中的数据仍然是旧的,从而发生不一致。
- 在极高并发的场景下,即使使用延迟双删策略,仍可能出现多个线程同时操作缓存和数据库的情况。举个例子:
-
脏读问题:
- 在延迟删除的这段时间内,如果有并发的读取请求,可能会读到数据库更新后的旧缓存。这在某些实时性要求高的应用中是不可接受的,比如金融系统或订单处理系统,必须保证读到的数据是最新的。
-
网络延迟和异常情况:
- 网络延迟、系统抖动等不确定因素也会影响延迟双删的效果。在分布式系统中,延迟双删依赖于多个系统的协作(如缓存、数据库、应用服务),如果某个环节出现异常,如缓存删除失败、网络延迟较大等,都可能导致数据不一致。
一致性问题的进一步讨论
严格来说,延迟双删策略提供的一致性保证是最终一致性,即在一段时间内,缓存和数据库的数据可能会出现不一致的情况,但在系统恢复到稳定状态时,缓存和数据库的数据最终会一致。
- 强一致性(Strict Consistency)是指系统中每次读操作都能够获得最近的写操作的结果,要求每次数据读取都是最新的。这种一致性在延迟双删策略中是无法保证的。
- 最终一致性(Eventual Consistency)则意味着经过一段时间后,系统的数据会变得一致,但在某些瞬间,数据可能不一致。延迟双删策略通常保证的是这一类一致性。
替代或补充的优化策略
对于高并发场景或者对一致性要求特别严格的系统,延迟双删策略可能不够,因此需要引入其他优化策略:
-
分布式锁:
- 在缓存更新时使用分布式锁(如 Redis 的
SETNX
加锁机制)确保只有一个线程能够更新缓存和数据库,避免并发写操作导致的缓存与数据库不一致。
- 在缓存更新时使用分布式锁(如 Redis 的
-
消息队列:
- 使用消息队列进行缓存更新操作,将数据库更新后的缓存操作通过消息队列异步处理,从而保证顺序性和一致性。
-
强制缓存更新:
- 对于实时性要求较高的系统,可以强制要求在每次读写操作时更新缓存,保证缓存和数据库的数据是一致的,代价是性能损失更大。
-
写通过策略(Write-through Cache):
- 直接在写入数据库时同步更新缓存,这样保证写入数据库的数据也一定进入缓存中,避免了缓存中存在旧数据的问题,但这增加了写操作的延迟。
-
读写分离:
- 可以通过数据库主从架构实现读写分离,将读取操作主要从缓存或者从库执行,减少对主库的压力,同时让主库专注于写操作并及时更新缓存。
强一致性的总结:
延迟双删策略虽然能在大部分场景下解决缓存与数据库不一致的问题,但它并不能保证强一致性,尤其是在高并发场景下或对一致性要求较高的系统中。其一致性保证更多的是最终一致性。如果业务系统对一致性要求较高,建议引入分布式锁、消息队列等其他优化策略来进一步提升一致性。
问题是为啥要延迟1s呢?不延迟可以吗?
延迟双删策略的核心目的是避免并发情况下缓存和数据库之间的短暂不一致问题。虽然延迟并不能阻止线程 B 并发操作,但它可以帮助我们在一个较短的时间窗口内修正数据的不一致性。我们来详细拆解一下这个问题:
问题核心:延迟 1 秒的目的
延迟的 1 秒钟确实是为了让数据库的更新有足够的时间被完全写入并提交,从而避免因为数据库更新延迟而导致的“脏数据”问题。具体来说,延迟 1 秒的主要目的是:
-
避免线程 A 更新数据库后立即读取脏数据:如果线程 A 在删除缓存和更新数据库后,马上读取数据库,可能会因为数据库还没有完全更新而读到旧数据。此时,如果缓存再次填充旧数据,会导致缓存不一致。延迟 1 秒的目的就是为了给数据库的更新操作留出足够的时间,让数据更新在数据库中被真正写入完成。
-
缓解线程 B 的并发读取问题:虽然延迟无法直接阻止线程 B 的并发操作,但如果线程 B 恰好在数据库还未更新完成时读取了旧数据并将其重新写入缓存,延迟后的第二次删除操作可以确保最终缓存中不会保留这些旧数据。因此,延迟 1 秒相当于一个最终修正手段,确保在并发场景下,旧数据不再保留太长时间。
并发情况下脏数据的问题
如果没有延迟,存在以下风险:
-
线程 A 更新数据库时可能存在延迟:数据库操作通常是有耗时的,尤其是在高并发场景下。如果线程 A 在数据库更新后立即读取缓存,可能读到的还是旧数据。如果没有延迟,线程 A 可能会直接填充缓存为旧数据。
-
线程 B 并发读取旧数据:在缓存删除和数据库更新之间的时间窗口内,线程 B 可能会读取旧的数据库数据,并将其重新写入缓存。如果没有延迟进行第二次缓存删除,旧数据将会长期存在于缓存中,导致缓存与数据库之间的数据不一致。
延迟并不是给线程 B 留的时间,而是给数据库写入时间
所以,延迟 1 秒的核心并不是为了让线程 B 等待,而是为了给数据库足够的时间完成更新,并确保缓存中不会保留因为并发读取而填充的旧数据。线程 A 的延迟删除缓存实际上是为了确保即便有线程 B 并发写入了旧数据,最终这些旧数据也会被删除,从而达到数据的一致性。
脏数据与缓存更新关系
如果线程 A 不进行延迟删除,那么在高并发的情况下:
- 数据库更新的速度可能跟不上线程 A 的缓存操作速度,导致线程 A 或其他线程读取到未更新的数据(脏数据)。
- 当并发请求(线程 B)读取旧数据时,这些旧数据会被重新写入缓存,导致缓存的数据和数据库中的数据不同步。
通过延迟删除缓存,可以确保线程 A 在更新数据库后,数据库中的数据有足够的时间更新完成,避免脏数据进入缓存。并且,即便有其他线程在缓存删除后的短时间内写入旧数据,延迟的第二次删除操作也能及时清除这些旧数据。
延迟1 秒的总结
延迟 1 秒的目的是为了给数据库更新操作预留时间,确保数据库在并发场景下能正确地完成更新,防止线程 A 或线程 B 在数据库还没更新完毕的情况下读取到旧数据。延迟删除缓存不是直接解决并发问题的手段,而是通过最终一致性的方式来确保缓存中不会长时间保留旧数据,减少缓存与数据库不一致的时间窗口。
如果并发场景特别复杂或者延迟时间难以估计,还可以引入其他手段(如分布式锁)来更加精细地控制缓存和数据库的同步问题。