【后端面经】【缓存】33|缓存模式:缓存模式能不能解决缓存一致性问题?-03 Refresh Ahead + SingleFlight + 删除缓存 + 延迟双删

简介: 【5月更文挑战第11天】Refresh Ahead模式通过CDC异步刷新缓存,但面临缓存一致性问题,可借鉴Write Back策略解决。SingleFlight限制并发加载,减少数据库压力,适合热点数据。删除缓存模式在更新数据库后删除缓存,一致性问题源于读写线程冲突。延迟双删模式两次删除,理论上减少不一致,但可能降低缓存命中率。选用模式需权衡优劣,延迟双删在低并发下较优。装饰器模式可用于实现多种缓存模式,无侵入地增强现有缓存系统。

Refresh Ahead


2024-05-12-13-19-21-image.png

Refresh Ahead是指利用CDC(capture Data Change)接口来异步刷新缓存的模式,这种模式在实践中也很常见,比如利用Canal来监听数据库的binlog,然后Canal刷新Redis。这种模式也有缓存一致性的问题,也是出在缓存未命中的读请求和写请求上。
2024-05-12-13-21-47-image.png

实际上,这个缓存一致性问题是可以解决的,也就是参考 Write Back 里面的策略。

如果读请求在回写缓存的时候,使用了 SETNX 命令,那么就没有什么大的不一致问题了。唯一的不一致就是数据写入到了数据库,但是还没刷新到缓存的那段时间。

SingleFlight

SingleFlight主要是为了控制住加载数据的并发量
2024-05-12-13-22-38-image.png

先简单介绍SingleFlight的原理,再补充它的优缺点

Singleflight模式是指当缓存未命中的时候,访问同一个key的线程或者协程中只有一个会真的加载数据,其他都在原地等待。

这种模式最大的优点就是可以减轻访问数据库的并发量,比如如果同一时刻有100个线程要访问key1,那么最终也只有1个线程去数据库中加载数据。这个模式的缺点是如果并发量不高,那么基本没有效果。所以热点之类的数据就很适合使用这个模式。

删除缓存

这算是业务中比较常见的用法,也就是在更新数据的时候先更新数据库,然后将缓存删除。
2024-05-12-13-25-27-image.png

删除缓存本身没有规定必须是业务代码来删除缓存,所以实际上也可以结合 Write Through 模式,让缓存去更新数据库,然后缓存自己删除自己的数据。
2024-05-12-13-25-43-image.png

这个模式依旧没有解决数据一致性的问题,但是它的一致性问题不是源自两个线程同时更新数据,而是源自一个线程更新数据,一个线程缓存未命中回查数据库。
2024-05-12-13-25-58-image.png

你在回答的时候要注意答出这一点。

删除是最常用的更新缓存的模式,它是指在更新数据库之后,直接删除缓存。这种做法可以是业务代码来控制删除操作,也可以结合 Write Through 使用。而且删除缓存之后会使缓存命中率下降,也算是一个隐患。如果偶尔出现写频繁的场景,导致缓存一直被删除,那么就会使性能显著下降。缓存未命中回查数据库叠加写操作,数据库压力会很大。

删除缓存和别的模式一样,也有一致性问题。但是它的一致性问题是出在读线程缓存未命中和写线程冲突的情况下。

然后你补充一句总结。

为了避免这种缓存不一致的问题,又有了延迟双删模式。

延迟双删

有两次删除操作
2024-05-12-13-27-04-image.png

延迟双删类似于删除缓存的做法,它在第一次删除操作之后设定一个定时器,在一段时间之后再次执行删除。

第二次删除就是为了避开删除缓存中的读写导致数据不一致的场景。
2024-05-12-13-27-25-image.png

那么是不是就不会有数据不一致的问题了?从理论上来说是可能的。第一个不一致出现在上图写入 a = 3 到第二次删缓存之间,还有一种不一致的可能如下图。
2024-05-12-13-27-37-image.png

但是这种可能性只是存在理论中,因为两次删除的时间间隔很长,不至于出现图片里的这种情况。所以你补充说明一下就可以了。

在这种形态之下,只需要考虑在回写缓存和第二次删除之间,数据可能不一致的问题。

紧接着再次说明这种模式的缺点。

延迟双删因为存在两次删除,所以实际上缓存命中率下降的问题更加严重。

选用什么模式

我觉得到这一步你已经非常困惑了,万一面试官问你应该使用哪个模式要怎么回答呢?坦白说,任何一种缓存模式都有各自的缺陷,所以你实际上选哪个都有好处,也都有问题。面试的时候你就可以根据自己的偏好来选择,只要分析清楚优劣,并解释清楚数据一致性问题就可以了。

如果你确实需要一个标准答案,那么你就回答延迟双删。

这么多模式里面,我比较喜欢延迟双删,因为它的一致性问题不是很严重。虽然会降低缓存的命中率,但是我们的业务并发也没有特别高,写请求是很少的。命中率降低一点点是完全可以接受的。

亮点方案:用装饰器模式实现缓存模式

这个亮点你可以考虑是否要使用,我建议你在实践中落地之后再拿去面试。但是你不需要把所有的模式都实现一遍,实现一下你项目中用到的就可以。

前面的缓存模式中,除了 Refresh Ahead 和 Cache Aside,其他的模式都可以使用装饰器模式来实现。我举一个使用缓存模式中 Read Through 模式的例子。你可以参考我给出的伪代码。

type Cache interface {
   
  Get(key string) any
  Set(key string, val any)
}

type ReadThroughCache struct {
   
  c Cache
  fn func(key string) any
}

func (r *ReadThroughCache) Get(key string) any {
   
    val := r.c.Get(key)
    if val == nil {
   
      val = r.fn(key)
      r.c.Set(key, val)
    }
    return val
}

你抓住关键词装饰器模式来描述这个解决方案。

我在我们公司利用装饰器模式,无侵入式地实现了其中的大部分模式。以 Read Through 为例,装饰器模式只需要在已有的缓存实现的基础上,为 Get 方法添加一个缓存中没有找到就去加载数据的额外逻辑就可以。

而且,如果你平时在公司的项目经历比较平淡,那么你完全可以在公司内部定义一个统一的 Cache 接口,提供基于 Redis 和本地内存的实现,同时提供这些缓存模式的实现,那么也算是一个比较有特色的项目了。

你就可以这样介绍你的项目。我在公司里面因为经常用到缓存,也经常使用缓存模式,所以我抽象了一个缓存接口,提供了基于 Redis 和本地内存的实现。在这个基础上,我还用装饰器模式实现了大部分缓存模式。对于开发者来说,他们只要会初始化装饰器就可以应用这个缓存模式。

后续你就可以和面试官讨论每一个缓存模式的细节。Beego 的缓存模块中有类似的实现,你可以参考。

目录
相关文章
|
2天前
|
缓存 NoSQL 中间件
【后端面经】【缓存】36|Redis 单线程:为什么 Redis 用单线程而 Memcached 用多线程?epoll、poll和select + Reactor模式
【5月更文挑战第19天】`epoll`、`poll`和`select`是Linux下多路复用IO的三种方式。`select`需要主动调用检查文件描述符,而`epoll`能实现回调,即使不调用`epoll_wait`也能处理就绪事件。`poll`与`select`类似,但支持更多文件描述符。面试时,重点讲解`epoll`的高效性和`Reactor`模式,该模式包括一个分发器和多个处理器,用于处理连接和读写事件。Redis采用单线程模型结合`epoll`的Reactor模式,确保高性能。在Redis 6.0后引入多线程,但基本原理保持不变。
21 2
|
3天前
|
缓存 NoSQL Redis
【后端面经】【缓存】36|Redis 单线程:为什么 Redis 用单线程而 Memcached 用多线程?--epoll调用和中断
【5月更文挑战第18天】`epoll`包含红黑树和就绪列表,用于高效管理文件描述符。关键系统调用有3个:`epoll_create()`创建epoll结构,`epoll_ctl()`添加/删除/修改文件描述符,`epoll_wait()`获取就绪文件描述符。`epoll_wait()`可设置超时时间(-1阻塞,0立即返回,正数等待指定时间)。当文件描述符满足条件(如数据到达)时,通过中断机制(如网卡或时钟中断)更新就绪列表,唤醒等待的进程。
32 6
|
4天前
|
NoSQL Redis 缓存
【后端面经】【缓存】36|Redis 单线程:为什么 Redis 用单线程而 Memcached 用多线程?
【5月更文挑战第17天】Redis常被称为单线程,但实际上其在处理命令时采用单线程,但在6.0后IO变为多线程。持久化和数据同步等任务由额外线程处理,因此严格来说Redis是多线程的。面试时需理解Redis的IO模型,如epoll和Reactor模式,以及其内存操作带来的高性能。Redis使用epoll进行高效文件描述符管理,实现高性能的网络IO。在讨论Redis与Memcached的线程模型差异时,应强调Redis的单线程模型如何通过内存操作和高效IO实现高性能。
33 7
【后端面经】【缓存】36|Redis 单线程:为什么 Redis 用单线程而 Memcached 用多线程?
|
5天前
|
缓存 数据库 NoSQL
【后端面经】【缓存】35|缓存问题:怎么解决缓存穿透、击穿和雪崩问题?--主从切换方案
【5月更文挑战第16天】该方案提出了解决Redis缓存穿透、击穿和雪崩问题的策略。通过使用两个或多个互为备份的Redis集群,确保在单个集群故障时,另一个可以接管。在故障发生时,业务会与备用集群保持心跳检测,并根据业务重要性分批转移流量,逐步增加对备用集群的依赖,同时监控系统稳定性。对于成本敏感的小型公司,可以采用低成本的单机或小规模自建Redis备份。此方案强调渐进式流量转移,以保护系统免受突然压力冲击。
16 1
【后端面经】【缓存】35|缓存问题:怎么解决缓存穿透、击穿和雪崩问题?--主从切换方案
|
7天前
|
缓存 数据库 算法
【后端面经】【缓存】35|缓存问题:怎么解决缓存穿透、击穿和雪崩问题?---解决缓存击穿和雪崩、限流
【5月更文挑战第15天】本文介绍了如何解决缓存击穿和雪崩问题。对于缓存击穿,采用singleflight模式,确保即使热点数据导致大量请求未命中缓存,也只允许一个请求真正查询数据,其他请求等待其结果。对于缓存雪崩,解决方案是在设置过期时间时添加随机偏移量,避免所有数据同时过期。偏移量应与过期时间成正比。此外,限流也是一个重要策略,可以在服务层和数据库层实施,以限制请求流量,保护数据库免受高并发压力。
12 0
【后端面经】【缓存】35|缓存问题:怎么解决缓存穿透、击穿和雪崩问题?---解决缓存击穿和雪崩、限流
|
7天前
|
存储 缓存 NoSQL
【后端面经】【缓存】35|缓存问题:怎么解决缓存穿透、击穿和雪崩问题?---解决缓存穿透
【5月更文挑战第14天】解决缓存穿透问题有两种策略。一是回写特殊值,当数据不存在时,在缓存中存储特殊值以标记,避免下次重复查询数据库。但此方法可能被恶意请求利用,浪费内存。二是使用布隆过滤器,预先判断数据是否存在,减少无效数据库查询。布隆过滤器虽有假阳性可能,但概率低,可接受。此外,可先查缓存再查布隆过滤器,优化正常请求的效率。两种方式各有优劣,实际应用需根据场景选择。
18 3
|
8天前
|
缓存 数据库 NoSQL
【后端面经】【缓存】35|缓存问题:怎么解决缓存穿透、击穿和雪崩问题?---缓存穿透、击穿和雪崩
【5月更文挑战第13天】本文讨论了三种常见的缓存问题:穿透、击穿和雪崩。缓存穿透发生时,请求的数据既不在缓存也不在数据库,可能导致数据库崩溃。缓存击穿指数据仅存在于数据库,热点数据的大量未命中请求会压垮数据库。缓存雪崩则是大量缓存在同一时间过期,引发数据库瞬间压力过大。为应对这些问题,需了解Redis部署(如Cluster或Sentinel)、故障恢复策略,以及公司如何保护数据库。解决缓存问题的经验和预防措施是面试中的重要话题。
13 0
【后端面经】【缓存】35|缓存问题:怎么解决缓存穿透、击穿和雪崩问题?---缓存穿透、击穿和雪崩
|
8天前
|
消息中间件 缓存 NoSQL
Redis经典问题:缓存雪崩
本文介绍了Redis缓存雪崩问题及其解决方案。缓存雪崩是指大量缓存同一时间失效,导致请求涌入数据库,可能造成系统崩溃。解决方法包括:1) 使用Redis主从复制和哨兵机制提高高可用性;2) 结合本地ehcache缓存和Hystrix限流降级策略;3) 设置随机过期时间避免同一时刻大量缓存失效;4) 使用缓存标记策略,在标记失效时更新数据缓存;5) 实施多级缓存策略,如一级缓存失效时由二级缓存更新;6) 通过第三方插件如RocketMQ自动更新缓存。这些策略有助于保障系统的稳定运行。
388 1
|
8天前
|
存储 消息中间件 缓存
Redis缓存技术详解
【5月更文挑战第6天】Redis是一款高性能内存数据结构存储系统,常用于缓存、消息队列、分布式锁等场景。其特点包括速度快(全内存存储)、丰富数据类型、持久化、发布/订阅、主从复制和分布式锁。优化策略包括选择合适数据类型、设置过期时间、使用Pipeline、开启持久化、监控调优及使用集群。通过这些手段,Redis能为系统提供高效稳定的服务。
|
8天前
|
缓存 NoSQL 关系型数据库
【Redis】Redis 缓存重点解析
【Redis】Redis 缓存重点解析
23 0