【后端面经】【缓存】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 的缓存模块中有类似的实现,你可以参考。

目录
相关文章
|
3月前
|
canal 缓存 NoSQL
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
根据对一致性的要求程度,提出多种解决方案:同步删除、同步删除+可靠消息、延时双删、异步监听+可靠消息、多重保障方案
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
|
2月前
|
存储 缓存 NoSQL
大数据-38 Redis 高并发下的分布式缓存 Redis简介 缓存场景 读写模式 旁路模式 穿透模式 缓存模式 基本概念等
大数据-38 Redis 高并发下的分布式缓存 Redis简介 缓存场景 读写模式 旁路模式 穿透模式 缓存模式 基本概念等
66 4
|
2月前
|
存储 缓存 NoSQL
深入理解后端缓存机制的重要性与实践
本文将探讨在后端开发中缓存机制的应用及其重要性。缓存,作为提高系统性能和用户体验的关键技术,对于后端开发来说至关重要。通过减少数据库访问次数和缩短响应时间,缓存可以显著提升应用程序的性能。本文将从缓存的基本概念入手,介绍常见的缓存策略和实现方式,并通过实例展示如何在后端开发中有效应用缓存技术。最后,我们将讨论缓存带来的一些挑战及其解决方案,帮助您在实际项目中更好地利用缓存机制。
|
3月前
|
机器学习/深度学习 缓存 NoSQL
深度学习在图像识别中的应用与挑战后端开发中的数据缓存策略
本文深入探讨了深度学习技术在图像识别领域的应用,包括卷积神经网络(CNN)的原理、常见模型如ResNet和VGG的介绍,以及这些模型在实际应用中的表现。同时,文章也讨论了数据增强、模型集成等改进性能的方法,并指出了当前面临的计算资源需求高、数据隐私等挑战。通过综合分析,本文旨在为深度学习在图像识别中的进一步研究和应用提供参考。 本文探讨了后端开发中数据缓存的重要性和实现方法,通过具体案例解析Redis在实际应用中的使用。首先介绍了缓存的基本概念及其在后端系统性能优化中的作用;接着详细讲解了Redis的常见数据类型和应用场景;最后通过一个实际项目展示了如何在Django框架中集成Redis,
|
4月前
|
缓存 网络协议 API
【API管理 APIM】APIM中对后端API服务的DNS域名缓存问题
【API管理 APIM】APIM中对后端API服务的DNS域名缓存问题
|
4月前
|
存储 缓存 JavaScript
深入理解后端开发中的缓存机制
【8月更文挑战第31天】本文将通过一个实际的后端开发案例,介绍如何有效地使用缓存来提高应用性能。我们将从基础概念开始,逐步深入到缓存策略的实施,最后通过代码示例展示如何在Node.js环境中实现一个简单的缓存系统。无论你是缓存新手还是希望优化现有系统的开发者,这篇文章都将为你提供实用的指导和启示。
|
4月前
|
存储 缓存 关系型数据库
Django后端架构开发:缓存机制,接口缓存、文件缓存、数据库缓存与Memcached缓存
Django后端架构开发:缓存机制,接口缓存、文件缓存、数据库缓存与Memcached缓存
73 0
|
4月前
|
存储 缓存 数据库
Django后端架构开发:信号与缓存架构开发
Django后端架构开发:信号与缓存架构开发
74 0
|
5月前
|
缓存 算法 API
深入理解后端开发中的缓存策略
【7月更文挑战第15天】缓存是提高后端系统性能和扩展性的关键机制之一。本文将深入探讨后端开发中缓存的应用,包括缓存的基本原理、类型、以及在实际应用中的策略。我们将从缓存的定义开始,逐步介绍缓存在数据库查询、API响应和分布式系统中的优化作用。通过实例分析常见的缓存模式,如LRU、LFU和FIFO,并讨论它们在不同场景下的适用性。最后,文章还将涵盖缓存一致性问题和解决方案,帮助读者构建高效且可靠的后端系统。
|
5月前
|
缓存 开发者 Java
java枚举消除冗余代码问题之需要延迟注册枚举到缓存问题如何解决
java枚举消除冗余代码问题之需要延迟注册枚举到缓存问题如何解决