面试官:如何保证缓存和数据库的一致性?

news2024/9/25 1:18:49

你好呀,我是苍何!

办公室里鸦雀无声,我木然的看着窗外射进来的阳光,它照在光滑的地板上,又反射到天花板上,再从天花板上反射下来时,就变成一片弥散的白光。

我在白光里偷偷放了一个恶毒的臭屁,如果是以前,同事们会偷偷捂住鼻子,然后目光看向旁边的人,没有人看向我,我也捂住鼻子,附和的来了句"谁又放毒了?"。

其实我却在心里偷偷笑,反正谁也猜不到是我。

如今办公室除了我再无一人,我还要在自己的臭屁下写文章,可能因此我的文章多少有些被「污染」。

但我依旧希望我的文章不是屁,最起码不能是臭屁。

那接下来,苍何给大家分享一个大厂面试常见问题:

如何保证缓存和数据库的一致性?,也即是既要又要怎么做?

缓存的重要性

缓存大家都并不陌生,说白了就是一种存储机制,用于暂时保存数据,以加速数据的读取和访问。

数据库好好的为什么要用缓存?关键点在于缓存快啊!

比如微信中的很大消息都是缓存在你本地手机的,(看到有自媒体博主发视频说能找回你 10 年前的微信聊天记录,大哥,前提是你 10 年不换手机?或者每次换手机你都能把所有聊天记录导出来?)

微信手机中的缓存是一种常见的本地缓存,对于开发来说本地缓存常用有以下这些:

  • JDK 自带的 HashMap 和 ConcurrentHashMap
  • Ehcache、Guava Cache、Spring Cache、Caffeine 等常见的本地缓存框架

除了本地缓存,还有常见的分布式缓存如常用的 Redis。本地缓存和分布式缓存很好区别,本地缓存和应用在同一个地方,而分布式缓存是可以独立部署在不同的服务器上的,比如 Redis 可以单机或者集群部署在不同的服务器上。

不管是本地缓存还是分布式缓存,都只有一个目的,那就是用空间换时间,用更多的存储空间来存储一些可能重复使用或计算的数据,从而减少数据的重新获取或计算的时间。

缓存介绍.png

缓存一致性问题

你有没有想过一个问题,既然一份数据同时在缓存中和数据库中都有,那这两者到底哪个是最新数据呢?如何保证其数据一致性呢?

那么导致不一致的原因主要有:

  • 缓存过期:缓存中的数据有一个生命周期,当数据过期后,如果没有及时更新,就会出现不一致的情况。
  • 写操作延迟:在执行写操作时,数据库更新和缓存更新的时间不同步,可能会导致缓存中的数据不一致。
  • 并发操作:多个并发操作同时进行,可能会导致数据更新时出现竞态条件,从而导致缓存和数据库数据不一致。
  • 缓存失效策略不当:使用不当的缓存失效策略,可能导致缓存中的数据无法及时更新,导致不一致。
  • 网络延迟或故障:由于网络延迟或故障,缓存服务器和数据库之间的通信出现问题,导致数据不一致。

常见的解决不一致问题有如下解决方案:

  • Cache Aside 模式:在读写操作中使用 Cache Aside 模式,确保在写操作后及时失效缓存中的数据。
  • 分布式锁:在并发写操作时使用分布式锁,确保同时只有一个操作能够更新缓存和数据库,避免竞态条件。
  • 双写一致性:在写操作时同时更新数据库和缓存,确保数据的一致性。
  • 延迟双删:在写操作时,先删除缓存中的数据,更新数据库后,再次删除缓存中的数据,确保缓存中数据的一致性。
  • 版本控制:在缓存和数据库中使用版本号或时间戳,确保数据更新时的一致性检查。
  • 监控和告警:对缓存和数据库中的数据进行监控,发现不一致时及时告警并处理。

而 PmHub 中主要围绕 Cache Aside 模式、分布式锁、监控和告警来解决一致性问题,之前章节有介绍分布式锁以及监控告警相关的能力,本节主要针对的 Cache Aside 模式。

常见缓存更新策略

先直接看苍何给做的汇总表吧:

模式名称描述优点缺点使用场景
Cache Aside Pattern (旁路缓存模式)读取数据时先检查缓存,缓存未命中则从数据库读取并更新缓存。写入数据时先更新数据库,然后使缓存失效。- 读取性能高
- 实现简单
- 首次请求数据一定不在缓存问题
- 写操作较频繁的话会导致缓存中数据被频繁删除,会影响缓存命中率
- 数据读取频率高,写入频率较低的场景
Read/Write Through Pattern (读写穿透模式)所有的读写操作都通过缓存进行,缓存负责同步数据库。- 数据一致性好
- 实现了读写操作的统一
- 实现复杂
- 依赖缓存的高可用性
- 数据读取和写入频率均较高的场景
Write Behind Pattern (异步缓存写入)写操作首先更新缓存,然后异步地将数据写入数据库。- 写操作性能高
- 减少数据库压力
- 存在数据丢失的风险
- 数据一致性较差
- 写操作频繁,且对实时一致性要求不高的场景

Cache Aside 模式概述

数据不一致的主要原因还是先写缓存还是先更新数据库的问题,不正确的方案我们就不阐述了,省的增加同学们的负担。那么 PmHub 采用的就是 Cache Aside 模式来保证缓存和数据库的一致性。

Cache Aside 模式其实就是读取数据时先检查缓存,缓存未命中则从数据库读取并更新缓存。写入数据时先更新数据库,然后使缓存失效。用一张图或许你更好理解一些:

Cache Aside时序图.png

Cache Aside 模式优点和局限

优点

  1. 读取性能高
    • 缓存命中时可以直接从缓存中读取数据,速度非常快,显著减少了数据库的访问压力。
  2. 实现简单
    • 该模式逻辑清晰,容易理解和实现,只需在读取和写入操作时分别处理缓存和数据库即可。使用的也比较多的一种方案了。
  3. 灵活性强
    • 程序员可以根据需要灵活地控制缓存的更新和失效策略,适应不同的应用场景和需求。
  4. 缓存利用率高
    • 只有在缓存未命中时才从数据库读取数据并更新缓存,避免了不必要的数据冗余。

局限

  1. 写操作较慢
    • 每次写操作都需要更新数据库并使缓存失效,这样的操作过程较长,导致写操作性能较低。
  2. 数据一致性挑战
    • 缓存和数据库之间可能会出现数据不一致的情况,特别是在高并发环境下,更容易出现缓存失效不及时或者更新顺序问题。
  3. 缓存预热问题
    • 初始时缓存为空,第一次读取时会有较大的延迟,直到缓存被逐渐填充起来。所以热点数据建议直接放入缓存
  4. 复杂的失效策略
    • 程序员需要设计合理的缓存失效策略,以确保缓存的数据及时更新,这增加了实现和维护的复杂性。具体怎么设置失效策略又体现了经验了。
  5. 过期数据风险
    • 如果没有合适的失效机制,缓存中可能会存在过期数据,从而返回错误或过时的信息给用户。

Cache Aside 模式在读取性能和实现简单性上有显著的优点,但在写操作性能和数据一致性上面临挑战。在使用这种模式时,需要根据具体应用场景设计合理的缓存更新和失效策略,以最大化其优点并最小化其局限。

对于 PmHub 业务来说,Cache Aside 模式就是最大化利用其优点以及最小化利用其局限性的实践。

PmHub 最佳实践

数据读取

原理很简单,就是先查询缓存,如果有就直接返回,没有就去数据库查询,然后再写入缓存。

这个在 PmHub 中很多地方的代码都有使用,随便举个例子吧:

/**
 * 根据键名查询参数配置信息
 *
 * @param configKey 参数key
 * @return 参数键值
 */
@Override
public String selectConfigByKey(String configKey) {
    String configValue = Convert.toStr(redisService.getCacheObject(getCacheKey(configKey)));
    if (StringUtils.isNotEmpty(configValue)) {
        return configValue;
    }
    SysConfig config = new SysConfig();
    config.setConfigKey(configKey);
    SysConfig retConfig = configMapper.selectConfig(config);
    if (StringUtils.isNotNull(retConfig)) {
        redisService.setCacheObject(getCacheKey(configKey), retConfig.getConfigValue());
        return retConfig.getConfigValue();
    }
    return StringUtils.EMPTY;
}

数据更新

更新数据的时候,先去更新数据库信息,然后再删除缓存,这个在 PmHub 中很多地方都有使用,比如在批量删除参数信息的场景下,先删除数据库信息,然后再删除缓存信息,代码如下:


/**
 * 批量删除参数信息
 *
 * @param configIds 需要删除的参数ID
 */
@Override
public void deleteConfigByIds(Long[] configIds) {
    for (Long configId : configIds) {
        SysConfig config = selectConfigById(configId);
        if (StringUtils.equals(UserConstants.YES, config.getConfigType())) {
            throw new ServiceException(String.format("内置参数【%1$s】不能删除 ", config.getConfigKey()));
        }
        configMapper.deleteConfigById(configId);
        redisService.deleteObject(getCacheKey(config.getConfigKey()));
    }
}

当然了,大家可以直接下载 PmHub 的源码,单体版本和微服务版本都可以看看里面具体的使用场景,如果有问题,也欢迎评论区留言,一起讨论。

面试

基于 PmHub 中缓存数据一致性解决方案,面试官可能会问到如下点,同学们只需要好好准备即可。

你们项目中是如何保证缓存和数据一致性的?

这个问题,建议大家学习完本章后,按照自己的理解作答,更加具有真实性

redis 有哪里数据类型

三分恶面渣逆袭:Redis基本数据类型

这个是比较基础的问题了,细节网上搜搜,背一背八股。

为什么你删除缓存,而不更新缓存呢?

主要原因有 2 点:
1、更新缓存浪费服务器资源:频繁的缓存更新可能导致缓存服务器的负载增加,通过删除缓存而不是频繁更新,可以减少缓存服务器的压力,提高系统整体性能。

2、避免脏读:在高并发环境下,如果在写操作时直接更新缓存,可能会导致并发读操作获取到未完全更新的数据,从而产生脏读现象。删除缓存可以避免这种情况,因为在缓存被删除后,所有读操作都会直接从数据库读取最新数据。

3、减少并发冲突:如果在写操作时直接更新缓存,可能会引发大量的并发冲突,特别是在频繁写操作的情况下。通过删除缓存,可以减少并发冲突的可能性,简化并发控制。

4、避免双写问题:在写操作时同时更新数据库和缓存,可能会导致双写不一致的问题。如果在更新数据库后失败了,缓存和数据库数据可能不同步。删除缓存可以避免这种双写问题,只需保证数据库写入成功。

先更新 DB,再删除缓存就是完美解决方案了吗?

其实面试官问这个问题,主要是想考哈你严禁性,通常来说,没有完美的解决方案,但你得能 BB 个所以然出来。

那这个问题其实就转换为让你说说 Cache Aside 的局限性,所以完全可以参考本章节关于局限性的解释来作答。

好啦,今天分享了关于数据和缓存一致性方案的一些见解,感谢阅读。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1976016.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

二百五十四、OceanBase——Linux上安装OceanBase数据库(四):登录ocp-express,配置租户管理等信息

一、目的 在部署OceanBase成功后,接下来就是登录ocp-express,配置租户管理等信息! 二、ocp-express网址以及账密信息 三、实施步骤 1 登录ocp-express 2 集群总览 3 租户管理 3.1 新建租户 3.2 配置新租户信息 剩下的几个模块了解即可&am…

redis实现的分布式锁redisson

redis服务宕机出现的概率很低,redis集群整体的思想是AP思想(优先保证高可用性) 如果非要保证业务数据强一致性建议采用CP思想,用zookeeper实现分布式锁。

C++自定义接口类设计器之模板代码生成四

关键代码 QStringList multis templateStr.split(\n);bool startConfig false;bool startVar false;bool startTemplate false;for (const auto& line : multis) {if(startConfig) {if(line.trimmed().startsWith("camealCase")) {auto name_val line.split…

Web开发-html篇-上

HTML发展史 HTML的历史可以追溯到20世纪90年代初。当时,互联网尚处于起步阶段,Web浏览器也刚刚问世。HTML的创建者是蒂姆伯纳斯-李(Tim Berners-Lee),他在1991年首次提出了HTML的概念。HTML的初衷是为了方便不同计算机…

TOA/TDOA测距定位,三维任意(>3)个锚节点,对一个未知点进行定位|MATLAB源代码

目录 程序介绍程序截图和运行结果程序截图运行结果 源代码代码修改建议 程序介绍 TOA/TDOA使用三点法测距,在空间中,有4个锚节点就可以定位,但如果有多个节点,定位效果会更好。 锚点不同时,修改程序中的向量和矩阵维度…

C++——哈希结构

1.unordered系列关联式容器 本节主要介绍unordered_map和unordered_set两个容器&#xff0c;底层使用哈希实现的 unordered_map 1.unordered_map是储存<key,value>键值对的关联式容器&#xff0c;其允许通过key快速查找到对应的value&#xff0c;和map非常相似&#x…

JavaFX布局-ToolBar

JavaFX布局-ToolBar 常用属性orientationpadding 实现方式Java实现fxml实现 容纳一组按钮的容器支持水平、垂直布局内容太多&#xff0c;会自动折叠 常用属性 orientation 排列方式&#xff0c;Orientation.VERTICAL、Orientation.HORIZONTAL flowPane.setOrientation(Orient…

【时时三省】(C语言基础)函数递归练习

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ——csdn时时三省 求字符串长度 求的是arr里面字符串的长度 abc后面还有一个\0为结束标志 在结算字符串长度的时候不算\0 所以它的长度是3 模拟实现一个strlen函数 str等于\0的时候就会结束返回count 如果…

纯css的loading效果

在之前的文章里面实现loading组件的封装 其实在日常生活中我们可以采用纯css的组件方式实现loading 的效果 其中<p>元素被绝对定位在其父元素的中心&#xff0c;并且其内部的文本大小和对行间距&#xff08;line-height&#xff09;是响应式的&#xff0c;基于视口宽度&…

《工程检索增强生成系统时的七个失败点》论文 AI 解读

周末使用 AI 速度了一篇 RAG 相关的论文&#xff0c;文中提到的【设计 RAG 系统时需要考虑的七个失败点】非常有价值&#xff0c;简单整理一下分享出来&#xff0c;大家如果感兴趣可以继续阅读原文。 论文名称&#xff1a;Seven Failure Points When Engineering a Retrieval A…

php反序列化靶机serial实战

扫描ip,找到靶机ip后进入 他说这是cookie的测试网页&#xff0c;我们抓个包&#xff0c;得到cookie值 base64解码 扫描一下靶机ip的目录 发现http://192.168.88.153/backup/&#xff0c;访问 下载一下发现是他的网页源码 通过代码审计&#xff0c;发现 通过代码审计得知&…

盘点和讯飞语音转文字一样好用的4款转换工具。

语音转文字能够快速准确地记录下人们的发言&#xff0c;使用相应的工具能够让我们不用担心遗漏重要信息&#xff0c;或者花费大量时间手动整理成文字内容。很多人都知道讯飞语音转文字&#xff0c;但是现在网络上也有很多其他好用的工具&#xff0c;就比如这4款&#xff1a; 1、…

Spring的配置类分为Full和Lite两种模式

Spring的配置类分为Full和Lite两种模式 首先查看 Configuration 注解的源码, 如下所示: Target({ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) Documented Component public interface Configuration {AliasFor(annotation Component.class)String value() defau…

Apache解析漏洞~CVE-2017-15715漏洞分析

Apache解析漏洞 漏洞原理 # Apache HTTPD 支持一个文件拥有多个后缀&#xff0c;并为不同后缀执行不同的指令。比如如下配置文件&#xff1a; AddType text/html .html AddLanguage zh-CN .cn# 其给 .html 后缀增加了 media-type &#xff0c;值为 text/html &#xff1b;给 …

WordPress资源下载类主题 CeoMax-Pro_v7.6绕授权开心版

CeoMax-Pro强大的功能 在不久的将来Ta能实现你一切幻想&#xff01;我们也在为此而不断努力。适用于资源站、下载站、交易站、素材站、源码站、课程站、cms等等等等&#xff0c;Ta 为追求极致的你而生。多风格多样式多类型多行业多功能 源码下载&#xff1a;ceomax-pro7.6.zip…

<数据集>航拍行人识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;7482张 标注数量(xml文件个数)&#xff1a;7482 标注数量(txt文件个数)&#xff1a;7482 标注类别数&#xff1a;1 标注类别名称&#xff1a;[people, pedestrian] 序号类别名称图片数框数1people5226385602pedes…

hcip作业1

写网关 r1 <Huawei>system-view [Huawei]sysname r1 [r1]interface GigabitEthernet 0/0/0 [r1-GigabitEthernet0/0/0]ip address 192.168.1.1 24 [r1]interface g 0/0/1 [r1-GigabitEthernet0/0/1]ip address 192.168.3.1 24 [r1]interface LoopBack 0 [r1-LoopBack0…

【算法/题目】:递归、搜索训练

✨ 吾与春风皆过客&#xff0c;君携春水揽星河 &#x1f30f; &#x1f4c3;个人主页&#xff1a;island1314 &#x1f525;个人专栏&#xff1a;算法训练 &#x1f680; 欢迎关注&#xff1a;&#x1f44d;点赞 …

117页PPT埃森哲-物流行业信息化整体规划方案

一、埃森哲-物流行业信息化整体规划方案 资料下载方式&#xff0c;请看每张图片右下角信息 埃森哲在物流行业信息化整体规划项目中的核心内容&#xff0c;旨在帮助物流企业通过信息技术的应用实现业务流程的优化、运营效率的提升以及市场竞争力的增强。以下是埃森哲在此类项目…

全面掌握 Kubernetes 对象的基本操作:从定义到实践

引言 Kubernetes 是当今最流行的容器编排平台之一&#xff0c;它通过自动化容器化应用的部署、扩展和管理&#xff0c;极大地提升了应用的可用性和可扩展性。在 Kubernetes 系统中&#xff0c;对象是其核心概念之一&#xff0c;是对系统状态的持久化描述。理解 Kubernetes 对象…