Redis缓存一致性

本文详细探讨了在Redis缓存系统中如何处理缓存一致性问题,包括内存淘汰策略(如LRU、LFU、惰性删除和定期删除),过期删除策略,以及主动更新机制(如CacheAsidePattern、Read/WriteThroughPattern和WriteBehindCachingPattern)。
摘要由CSDN通过智能技术生成
Redis缓存一致性

  在采用Redis作为缓存系统的架构设计中,缓存一致性是一个关键问题。特别是在“先访问缓存再访问数据库”的策略下,可能会由于各种原因导致缓存与数据库中的数据不一致。为了解决这个问题,Redis提供了多种缓存更新策略,主要包括内存淘汰过期删除以及主动更新机制

内存淘汰

  内存淘汰策略是在Redis内存达到预设限制时触发的一种机制,以位处Redis服务器的稳定运行。当内存不足时按照内存淘汰策略,自动进行部分数据的淘汰,下次查询时更新缓存。淘汰策略的执行过程:客户端发送一个命令给Redis,比如要写入一个新的键值对或者是其他可能会增大内存使用的命令;Redis收到命令后,首先检查当前已使用的内存量是否超过了预先配置的maxmemory阈值;如果内存使用量超出限制,将按照配置的内存淘汰策略来决定要从内存中删除哪些键值对来释放空间。
  内存淘汰策略如下:

  • no-eviction。默认策略。如果新的写入操作会导致Redis内存超出设定的最大限制,则该写入请求会被拒绝。只读请求可以正常执行。
  • allkeys-lru。当写入新数据后的内存超过限定值时,Redis会基于LRU(最近最少使用)从所有key中选择并淘汰最近最少使用过的key
  • volatile-lru。与allkeys-lru类似,但在内存紧张时仅针对设置了过期时间的key应用LRU淘汰规则。
  • allkeys-random。当写入新数据后的内存超过限定值时,从所有key中随机淘汰key,不考虑key的访问频次或过期时间
  • volatile-ttl。优先淘汰即将过期的key,按照key的剩余生存时间(Time to Live, TTL)决定淘汰顺序,TTL越短的key越可能被淘汰。
  • allkeys-lfu。当写入新数据后的内存超过限定值时,Redis基于LFU(最少频率使用)从所有key中淘汰使用频率最低的key
  • volatile-lfu。类似allkeys-lfu。筛选淘汰对象时,仅关注设置了过期时间的key,并依据使用频率进行淘汰。

  LRU(Least Recently Used 最近最少使用):在 Redis 4.0 之前,LRU 算法的工作原理是基于一些采样机制来估算哪些键可能是最近最少使用的。当配置了 LRU 淘汰策略后,Redis 在内存不足时会尝试从所有数据集中找出最近最少访问的键,并将其从内存中移除,释放空间给新的数据。
  LFU(Least Frequently Used 最不经常使用):Redis 4.0 引入了真正的 LFU 算法支持,LFU 更加关注键的访问频率,而非单纯依赖访问时间。LFU 使用了一个计数器来跟踪每个键的访问频率,当需要淘汰数据时,会选择访问次数最少的键进行淘汰。随着时间推移,LFU 还会逐渐降低访问次数较少的键的权重,防止长期未访问但早期访问频繁的键一直占用内存。Redis 实现的 LFU 算法同样进行了优化,它不是一个纯正的 LFU 实现,而是混合了访问时间和访问频率两个因素,避免了纯粹 LFU 可能存在的“缓存污染”问题。

过期删除

  Redis作为一种缓存工具,被广泛应用于存储具有有效期的数据,比如Session管理、限时活动数据等。在这些场景下,数据有一个明确的生命周期,到达一定时间后应当自动失效,不再被访问或使用。**过期删除策略是专门针对这类数据设计的,用于在键值对达到预设的过期时间后自动将其从数据库中移除,无需等待内存达到上限时再由内存淘汰策略处理。**内存淘汰策略主要解决内存资源受限时如何合理释放内存空间,通常是在内存使用达到配置的最大限制时才会起作用,其目的是为了避免Redis因内存耗尽而崩溃,而非根据数据的实际有效期来管理数据。

  • 定时删除。key设置了过期时间,一旦过期立即删除。优点:对内存友好。缺点:定时器消耗CPU资源,以时间换空间。
  • 惰性删除。过期key不会马上被删除,而是继续保存在内存中。当客户端请求访问某个key时,Redis会检查key是否已过期。如果key已过期,Redis不会返回key对应的值,而是直接在访问时删除key,并返回nil表示key不存在。
    • 优点:资源节省。只有当客户端请求访问key时,才会去检查删除过期的key,不会因为频繁检查所有key的过期时间而占用额外的CPU资源。
    • 缺点:如果大量key过期后未被访问,这些过期key会继续占用内存,可能导致内存浪费。对于不活跃的过期key,惰性删除无法主动及时清理,可能影响到新数据的存储。
  • 定期删除。Redis 会定期进行过期键的扫描(默认每秒进行十次),并在扫描过程中随机检查并删除已过期的键。定期删除不是一次性检查所有键,而是通过采样方式来避免一次性扫描整个数据库造成较大的CPU负担。Redis 会动态调整定期删除策略的执行频率,以保持在内存紧张时有足够的驱逐力度,而在内存充足时减少不必要的计算资源消耗。
    • 优点:会主动扫描并清理过期key,避免过期key长时间占用内存资源,使得Redis可以在资源使用和内存回收之间找到平衡点。
    • 缺点:需要额外的CPU和时间资源执行扫描和清理工作,如果扫描过于频繁或扫描范围过大,可能会影响Redis服务器的性能。
主动更新

  当应用Redis作为缓存时,数据源通常是持久化的数据库,而数据库中的数据可能因为用户的写操作而发生变化。如果不采取措施,Redis缓存中的数据就会与数据库中的数据产生不一致,进而导致读取缓存时获取到的是过期或无效的数据。**主动更新旨在解决这一问题,确保当数据库的数据发生变化时,Redis缓存中的对应数据也能及时更新,保持与数据库的一致性。**主动更新大致分为:Cache Aside Pattern(旁路缓存模式)Read/Write Through Pattern(直写式/读写穿透)Write Behind Caching Pattern(异步缓存写入)

Cache Aside Pattern

  • 最经典常用的缓存策略,适用于读多写少的场景。该策略中,缓存和数据库独立运作,应用程序负责维护二者之间的数据同步。
  • 写操作时,当应用程序需要更新数据时,先更新数据库,然后删除相应的Redis缓存。
  • 读取时,先读缓存,缓存存在直接返回;缓存不存在则从数据库中重新加载最新数据并将其存在缓存中。
  • 优点
    • 结构简单,易于实现和理解;
    • 对数据库压力小,只在缓存失效时才读取数据库;
    • 缓存和数据库解耦,适合复杂的分布式系统。
  • 缺点
    • 数据更新后,缓存的更新是异步的,存在短暂不一致窗口期;
    • 需要额外代码来管理缓存的更新和失效;
    • 高并发写入场景下,可能会频繁引发缓存失效,导致大量的缓存重建请求。

Read/Write Through Pattern

  • 读写穿透。在该策略中,读写操作均通过缓存层完成,缓存层负责与数据库的交互,保证缓存和数据库的数据一致性。
  • 写操作时:当应用程序发起请求,先查找Redis缓存,如果缓存中有相应数据,先更新缓存,然后同步更新到数据库。如果缓存中没有数据,直接更新数据库,并将更新后的数据放入缓存中。
  • 读操作时:应用程序发起读请求,缓存命中直接返回缓存数据,缓存未命中时,从数据库读取数据,并将数据存入缓存后再返回给客户端。
  • 优点
    • 写操作同时更新缓存和数据库,保持二者同步;
    • 减轻了应用程序处理缓存更新的复杂度。
  • 缺点
    • 每次写操作都需要同时操作缓存和数据库,增加了写操作的延迟和网络负载;
    • 如果缓存层和数据库层同时出现问题,可能会影响到整个系统的可用性;
    • 对缓存服务要求搞,需要缓存服务能够很好地与数据库服务协同工作。

Write Behind Caching Pattern

  • 异步缓存写入,该策略主要减少对数据库的直接写操作,提高写性能。
  • 写操作时,应用程序先将缓存更新写入Redis缓存中,缓存系统将这些更新暂存,并在一个合适的时间(例如,达到一定数量的更新或者一定时间间隔后)批量将写入数据库中;
  • 读操作时,先读缓存,缓存存在直接返回;缓存不存在则读数据库,然后把读取的数据库数据存入缓存。
  • 优点
    • 性能最好,通过批处理方式异步写入数据库,减少数据库操作次数和网络交互;
    • 减少了对数据库的压力,适合高并发写入场景。
  • 缺点
    • 数据库可能存在短暂不一致,即缓存中的数据可能比数据库的新;
    • 如果在数据异步会写到数据库前缓存挂掉,可能会丢失这部分更新;
    • 如果系统故障恢复期间,需要处理缓存和数据库间的不一致状态。
缓存更新策略的实现方式
先更新缓存,再更新DB
  • 并发写场景下,如果所有线程都是先更新缓存再更新DB,线程A在更新缓存后还未完成对DB的更新时,线程B可能开始更新相同的缓存和DB。这时,如果线程B的更新由于线程A完成,那么即使线程A最终完成了数据库的更新,也会导致缓存种存储的数据与DB种的数据不一致。
先更新DB,再更新缓存
  • 并发写情况下,所有线程都是先更新DB后更新缓存。如果线程A在更新DB后,线程B可能在同一时间段内完成DB更新并开始更新缓存。此时,线程A在更新缓存时,可能会覆盖线程B刚刚更新的缓存内容,从而导致缓存与DB的不一致。
先删除缓存,再更新DB
  • 并发写场景下,所有线程都是先删除缓存再更新DB。这种方法可以确保在更新数据库前缓存已经被清空,因此不会出现缓存和DB的数据不一致。

  • 并发读写场景,可能存在这样的情况:线程A在删除缓存后,线程B在读取不到缓存的情况下查询DB并将旧数据写入缓存,随后线程A才更新DB,此时缓存中的数据就会落后于DB。

先更新DB,再删除缓存
  • 并发写场景下,所有线程都是先更新DB再删除缓存,无论哪个线程先更新DB再删除缓存,缓存都会被删除,不会导致缓存和DB不一致。例如:假设线程A先更新DB,紧接着线程B也更新DB,然后线程B删除缓存,线程A删除缓存
  • 并发读写场景下,线程A在更新DB后,线程B可能在缓存被删除前读取到旧缓存数据并以此为基础更新缓存,导致缓存中存储的是旧数据。
延迟双删
  • 为应对并发读写场景下的数据不一致问题,提出了延迟双删策略。延迟双删是基于先删除缓存再更新DB的基础上的改进:在更新DB后延迟一定时间,再次删除缓存。在该策略中,受限删除缓存,然后更新DB,并在完成DB更新后经过一段延迟再次删除缓存。这段延迟是为了确保在第二次删除缓存时,所有读取请求都能够查询到已更新的数据库内容,从而在重新加载缓存时能够保持数据一致性。

  • 并发读写场景下,假设线程A执行写操作,线程B执行读操作。线程A先删除缓存,线程B查询,未命中,线程B再查询DB,线程B根据查询的DB结果更新缓存,线程A更新DB,线程A延时删除缓存。线程A第一次删除缓存后,线程B根据查询的DB结果更新缓存,此时查询得到的结果是旧数据,线程A延迟第二次删除缓存后,后续查询DB,重新写入缓存,不会导致缓存和DB数据不一致

  • 虽然该方法不能提供强一致保证,但能够在很大程度上减少缓存与DB不一致的情况。

异步删除缓存
  • 异步更新缓存是基于先更新DB再删除缓存的基础上的改进,通过引入消息队列,更新DB后异步地执行缓存删除操作。
  • 并发写场景下,多个线程地删除操作会被添加到消息队列中依次执行。线程A先更新DB,线程B再更新DB,线程B把删除缓存放入消息队列中,线程A紧接着把删除缓存放入消息队列中,最后消息队列删除缓存
  • 并发读写场景下,线程A更新DB,线程B查询缓存,命中返回,线程A把删除缓存放入消息队列,最后消息队列删除缓存。其实,异步删除缓存期间,读线程获取的缓存是旧数据,短暂出现数据不一致,异步删除缓存后最终会一致
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值