一文讲透缓存方案及常见问题——初篇

2023-10-26

Hello 大家好,今天跟大家聊的一个话题就是:缓存

目前,面向C端的服务架构中,除开管理后台等访问量很少、实时性要求较高的服务可不使用缓存外,缓存已成为高性能分布式系统里不可或缺的一环。

本文不打算过多涉及具体的缓存组件如Memcached,Redis等,将缓存做一个整体的概念来展开,至于具体的缓存组件及各自的优缺点、工作原理,则是更细一个维度的问题了。

缓存的本质

缓存的目的其实说白了就一点:加速访问。稍微细分一下可分为加速数据访问加速计算:前者是将热点数据暂存在访问速度更高的硬件中,例如内存的访问速度比机械硬盘的速度高上几个数量级,那么就可以将内存作为硬盘的缓存介质。而内存的速度相对CPU的计算速度又慢了许多,因此现在都会在CPU和内存间再设计几级速度更快的缓存,以加速CPU的数据访问。

而后者则是在假设某份数据并不是直接存储在数据库中,而是通过许多数据,通过复杂的业务运算,乃至加入外部服务的部分结果聚合计算而成。一则需要访问过多的数据,二来涉及到外部调用的网络开销,那么即使使用同样的存储介质,也可以达到加速访问的目的。

此外,缓存的存在是为了对应大量的查询请求,因此如果查询请求量不大,或者数据更新频繁导致缓存频繁失效,就需要斟酌是否要引入缓存。

一致性问题

设计架构的核心就是做取舍(trade off)。引入一个组件,就要考虑随之带来的问题。除开必然带来的额外维护成本外,缓存还带来了分布式系统的一个典型问题:一致性问题

其实数据只要分布在多个节点,那么就不可能完全一致,多份数据达成一致总需要一定的同步时间。所谓强一致性,只不过达成数据一致的时间相对较短,而弱一致性则是这个时间相对较长。

而自缓存引入的那一天,就带来这样的一致性问题:如何保证数据库和缓存服务的数据一致性?为了解决这个问题,前辈们讨论总结了许多种数据更新策略。

缓存更新策略

下面我们对缓存的一些更新策略做一个归纳分析,需要说明的是:以下策略暂时不讨论更新失败、网络异常问题,这是所有更新策略都存在的问题,放在最后统一讨论。

先更新缓存,后更新数据库

这个策略带来问题显而易见,是个典型的并发问题,考虑如下场景:

假设原始数据为a = 1(竖向为时间轴)

线程 1 2
t1 修改缓存数据:a = 2
t2 修改缓存:a=3
t3 修改数据库:a=3
t4 修改数据库:a=2

经过上述场景,数据库和缓存内的数据就不一致了,有问题的场景还有很多:

线程 1 2
t1 读取缓存:数据不存在
t2 修改缓存数据:a=2 查询数据库:a=1
t3 修改数据库:a=2
t4 设置缓存:a=1

先更新数据库,后更新缓存的策略有同样的问题,不再赘述。而即使不考虑并发问题,这个方案也有一个小问题:数据虽然修改了,但是不一定马上就要读取,设置缓存这个操作是否有些浪费?

先删缓存,再更数据库

既然更新会有并发修改的问题,那我把更新缓存换成幂等的删除操作,问题是否就解决了?再考虑如下场景:

线程 1 2
t1 删除缓存
t2

读取缓存不存在;

查询数据库:a=1

t3 修改数据库:a=2
t4 设置缓存:a=1

可以看到,由于先删除了缓存,那么在数据库修改成功之前,只要有请求访问,就会在缓存里设置脏数据,因此这个方案缺点也比较明显。

先更新数据库,后删除缓存

这应该是目前使用得比较多个一个方案,成本低,缺点少,配合缓存过期时间,几乎没有并发问题。这个方案解决了并发修改时的一致性问题,数据库本身有锁支持并发修改,缓存操作由于是幂等的删除操作,多线程执行也不影响。

即使修改数据库前有线程读取到旧版本的数据,只要在删除缓存前写入的旧缓存,数据会被操作删除掉,配合缓存过期时间,几乎没有问题。之所以说“几乎没有问题”,是因为理论上还是存在下列场景,旧数据缓存在删除操作之后写入:

线程 1 2
t1 读取缓存:数据不存在
t2 查询数据库:a=1
t3 修改数据库:a=2
t4 删除缓存:a

t5 设置缓存:a=1

但是这个场景出现的概率不高,因为缓存几乎都是基于内存的,写缓存比操作数据库更快,较难出现线程1修改完数据库及缓存,线程2才完成缓存设置的情况,即使少量出现这个情况,因为缓存一般都设置有有效时间,所以脏数据最多存活一个缓存生命周期后,就会被淘汰掉。

延时双删

延时双删是对上述先更后删方案的补充完善。其流程是:

  1. 删除缓存

  2. 更新数据库

  3. 休眠一段时间

  4. 再次删除缓存

其中步骤3就是进一步减少先更后删方案的并发访问问题出现的概率,但我个人不是很推崇这个方案,原因如下:

  1. 休眠实现不优雅,且不同业务具体的休眠时长还需要观察调试,取一个平衡值,有实施成本。

  2. 影响修改操作的吞吐量

  3. 只是进一步减少问题概率,仍不能确保解决问题

尤其是第三点,如果业务不能容忍上述不一致场景,对数据有较强一致性要求,就应该采用更可靠的方案来实现,本方案采用延时,只是进一步减少异常场景的概率,并不是避免。牺牲了吞吐量还不能保证完全解决问题,就显得有些食之无味。

因此大部分业务场景,其实采用先更后删的方案是比较理想的trade off,成本小,方案也不复杂,但我们还是应该探究一下,假设我们少量的不一致都无法接受,应该怎么设计方案策略?这就是接下来要讨论的分布式事务问题。

分布式事务

上述策略还未讨论的一个问题是,网络原因更新失败了又该如何处理?以先更后删为例,更新数据库失败自然可以直接响应失败,但删除缓存失败了要如何补救呢?

其实保证数据库、缓存两个节点的数据一致性,是一个最简单的分布式事务问题。分布式事务的解决方案有很多:2PC, 3PC, TCC, Seata等,读者有兴趣可以自行查阅相关资料。个人建议缓存的场景使用最终一致的解决方案即可。

最终一致可以使用消息中间件的事务消息,或者使用本地消息表来实现。

用事务消息的实现流程是:

  1. 先发送一个事务消息,此时消息处于prepare状态,消费者还拉取不到

  2. 更新数据库

  3. 发送该事务消息的commit信号

  4. 消费者订阅事务消息,执行缓存相关操作

如果1,2任何一步失败,则整个事务直接失败;如果3失败,消息队列会对prepare的消息定时执行业务回调,来向业务方确认该消息是应当提交还是回滚。如果4失败,可依赖消息系统执行消息重发。由此保证了数据的最终一致性。

但事务消息的方案也有相应的问题:事务消息性能不佳(相对普通消息),且只有部分消息中间件支持。是否值得引入特定的消息中间件解决这个问题,又是另一个需要考虑的问题。

本地消息表方案相对简单一些,在更新数据库时,在消息表里也插入一条要删除的缓存的数据,这个可以用数据库的事务特性来支持。 然后定时任务扫描消息表执行缓存的清除操作。

缓存服务常见的三个问题

除开一致性外,缓存还需要考虑其他维护问题,比较典型的缓存问题有三个:缓存雪崩、缓存穿透、缓存击穿

缓存雪崩

缓存雪崩的场景是:大量缓存短时间内同时失效,请求好似雪崩一样涌向数据库。大部分使用缓存的场景,基于成本的考量,数据库都不会达到能单独支撑全量查询的性能,因此会造成数据库负载升高乃至宕机。

常规的解决方案也比较简单,设置缓存过期时间时,采用固定业务时间+随机几分钟的波动时间,减少数据同时过期的概率。

缓存穿透

所谓缓存穿透,是指访问业务上不存在的数据,由于缓存中没有数据,每次查询都打到数据库上。如果有大量的恶意穿透请求就会影响到数据库。常规解决方案有二:

  1. 设置短时间的空缓存。如果查询数据不存在,设置一个空数据到缓存内,有效时间可以设置短一些(例如一分钟),短时间读取到这个空内容时,就知道这个数据是不存在的,直接返回即可,不用再查数据库。但只能解决同样的key的多次访问,不能应对一直更换不同key的恶意请求。

  2. 使用布隆过滤器

布隆过滤器思路是用多个hash函数,将一个数据映射到多个不同的hash槽上,查询数据时,检查这对应的几个槽位是否都有值,如果没有值则说明数据不存在,都有值则说明数据可能存在(因为hash冲突)

使用布隆过滤器时,只要将全量的业务数据映射好,查询时,只有布隆过滤器显示可能存在的,才正常进入业务流程,否则直接返回数据不存在。但最初的布隆过滤器为了节约空间,采用bit数组存储,只能表示0或1,这就造成了布隆过滤器里数据只能新增,不能删除,要解决这个问题可以将bit数组扩展为数字数组(如byte, int),采用计数法记录,并增加数据删除的处理逻辑,当然空间占用会增多,读者有兴趣可自行查阅相关资料。

缓存击穿

缓存雪崩是大量key过期带来的问题,缓存击穿则是热点key过期,在一个请求成功加载数据到缓存的过程中,针对该key的请求大量打到数据库。

我个人了解到的解决思路也有两个:

  1. 使用锁限制访问数据库。同一个时间只有一个请求落到数据库。(这个场景下,个人认为单体锁和分布式锁区别不是很大)

  2. 主动加载热点数据到缓存。例如,一个热点数据,如果其过期时间是一个小时,那么定时任务每隔五十分钟就主动加载一下数据库的数据到缓存,直接避免热点数据过期的情况。

其他问题

其实笔者之前面试初中级应聘者的时候,缓存相关问题不喜欢问上述三个缓存八股文的问题,因为并不能真正考察应聘者的技术能力,只能说明应聘者查阅过相关知识点。真正缓存的问题绝不仅是上面几个典型的问题。

本系列缓存暂时分成两篇,这篇总结了缓存的一些基本概念及常见问题和解决方案,下一篇准备介绍一些进阶知识点,敬请期待。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

一文讲透缓存方案及常见问题——初篇 的相关文章

  • Stackexchange.redis 缺乏“WAIT”支持

    我在客户端应用程序正在使用的负载均衡器后面有 3 个 Web API 服务器 我正在使用这个库来访问具有一个主服务器和几个从服务器的 Redis 集群 目前不支持 WAIT 操作 我需要此功能来存储新创建的用户会话并等待它复制到所有从属服务
  • 使用 AWS ElastiCache 请求中的 Airflow CROSSSLOT 密钥未散列到同一插槽错误

    我在 AWS ECS 上运行 apache airflow 1 8 1 并且有一个 AWS ElastiCache 集群 redis 3 2 4 运行 2 个分片 2 个启用多可用区的节点 集群 Redis 引擎 我已经验证气流可以毫无问题
  • 如何让客户端下载动态生成的非常大的文件

    我有一个导出功能 可以读取整个数据库并创建一个包含所有记录的 xls 文件 然后文件被发送到客户端 当然 导出完整数据库的时间需要大量时间 并且请求很快就会以超时错误结束 处理这种情况的最佳解决方案是什么 例如 我听说过使用 Redis 创
  • Redis键空间事件不触发

    我有两个 Redis 客户端 在一个文件中我有一个简单的脚本设置并删除了 Redis 键 var redis require redis var client redis createClient 6379 127 0 0 1 client
  • 为什么Redis中不建议使用KEYS?

    在Redis中 建议不要使用按键命令 https redis io commands KEYS 为什么会这样呢 是因为它的时间复杂度是 O N 吗 或者是别的什么原因 我做了下面的实验来证明KEYS命令有多么危险 当带有 KEYS 的一个命
  • Docker-compose Predis 不通过 PHP 连接

    我正在尝试使用 docker compose 将 PHP 与 redis 连接 docker compose yml version 2 services redis image redis 3 2 2 php image company
  • redis-cli 重定向到 127.0.0.1

    我在PC1上启动Redis集群 然后在PC2上连接它 当需要重定向到另一个集群节点时 它会显示Redirected to slot 7785 located at 127 0 0 1 但应该显示Redirected to slot 7785
  • Spring Data Redis JedisConnectionException:流意外结束

    雷迪斯3 0 5Spring数据Redis 1 3 6绝地武士2 6 3 我们的 Web 应用程序通过 pub sub 从 Redis 接收数据 还以键 值对的形式在 Redis 上执行数据读 写 读 写发生在监听线程 独立监控线程和htt
  • Caffeine Expiry 中如何设置多个过期标准?

    我正在使用 Caffeine v2 8 5 我想创建一个具有可变到期时间的缓存 基于 值的创建 更新以及 该值的最后一次访问 读取 无论先发生什么都应该触发该条目的删除 缓存将成为三层值解析的一部分 The key is present i
  • 如何测试我的 Redis 缓存是否正常工作?

    我已经安装了 django redis cache 和 redis py 我遵循了 Django 的缓存文档 据我所知 以下设置就是我所需要的 但我如何判断它是否正常工作 设置 py CACHES default BACKEND redis
  • 在 sidekiq 上配置 redis 身份验证

    我想我错过了一些东西 因为我在文档中找不到如何编写 redis 实例的用户名和密码以与 sidekiq 一起使用 有没有办法做到这一点 或者是通过 ENV 变量 Sidekiq 将无法识别的 Redis 选项直接传递给 Redis 驱动程序
  • 使用 Sentinels 升级 Redis 的最佳实践?

    我有 3 个 Redis 节点 由 3 个哨兵监视 我进行了搜索 文档似乎不清楚如何最好地升级此类配置 我目前使用的是 3 0 6 版本 我想升级到最新的 5 0 5 我对这方面的程序有几个疑问 升级两个大版本可以吗 我在我们的暂存环境中执
  • Redis、会话过期和反向查找

    我目前正在构建一个网络应用程序 并想使用 Redis 来存储会话 登录时 会话会使用相应的用户 ID 插入到 Redis 中 并且过期时间设置为 15 分钟 我现在想实现会话的反向查找 获取具有特定用户 ID 的会话 这里的问题是 由于我无
  • 有没有办法用Lettuce自动发现Redis集群中新的集群节点IP

    我有一个Redis集群 3主3从 运行在一个库伯内斯簇 该集群通过Kubernetes 服务 Kube 服务 我将我的应用程序服务器连接到 Redis 集群 使用Kube 服务作为 URI 通过 Redis 的 Lettuce java 客
  • Spring Data JPA Redis:无法编写基于自定义方法的查询

    我已经使用 Redis 配置了 Spring Data JPA 并使用RedisRepositorieswith 提供了类似的方法find findAll 所有这些方法似乎都工作得很好 但我无法编写我的自定义方法 RedisEntity f
  • redis dump.rdb / 保存小文件

    Context 我正在使用redis 数据库小于 100 MB 但是 我想进行每日备份 我也在 Ubuntu Server 12 04 上运行 当输入 redis cli save 我不知道 dump rdb 保存到哪里 因为 redis
  • Scala 使用的 Redis 客户端库建议

    我正在计划使用 Scala 中的 Redis 实例进行一些工作 并正在寻找有关使用哪些客户端库的建议 理想情况下 如果存在一个好的库 我希望有一个为 Scala 而不是 Java 设计的库 但如果现在这是更好的方法 那么仅使用 Java 客
  • 使用redis进行树形数据结构

    我需要为基于树的键值开发一个缓存系统 与Windows注册表编辑器非常相似 其中缓存键是字符串 表示树中到值的路径 可以是原始类型 int string bool double 等 或子树本身 例如 key root x y z w val
  • Laravel Redis 配置

    我目前正在使用 Laravel 和 Redis 创建一个应用程序 几乎一切都工作正常 我按照文档中的说明扩展了身份验证 用户可以订阅 登录 注销 我可以创建内容 所有内容都存储在 Redis 中 但我有一个问题 我无法运行 php arti
  • 使用环境变量在 redis.conf 中设置动态路径

    我有一个环境变量MY HOME其中有一个目录的路径 home abc 现在 我有一个redis conf文件 我需要像这样设置这个路径 redis conf pidfile MY HOME local var pids redis pid

随机推荐

  • 报错Uncaught (in promise) TypeError: Invalid attempt to spread non-iterable instance.

    报错Uncaught in promise TypeError Invalid attempt to spread non iterable instance In order to be iterable non array object
  • Jlink无法使用解决方法

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 前言 一 检查问题 二 尝试重新烧录固件 1 烧录固件 后续 前言 公司使用的Jlink突然无法使用了 LED灯也不亮了 于是抱着修复Jlink的心态开始了 一 检
  • 【翻译】 迁移到Python 3

    本文由LWN用户为您带来LWN net的订阅者使得这篇文章 以及它周围的一切 成为可能 如果您喜欢我们的内容 请购买订阅 使下一组文章成为可能 2011年2月9日 本文由Ian Ward提供 Python 3 0于2008年底发布 但到目前
  • 用jmeter写登入脚本

    初学jmeter时 都介绍用jmeter设置代理服务器 或者badboy来录制脚本 但是真正运用到实际项目中后 发现这两个录制脚本的方法都不是很好用 用jmeter设置代理服务器录制的时候 我们公司的系统点击登入的时候 会提示系统无响应 我
  • 字符串格式化

    String类的静态format 方法用于创建格式化的字符串 format 方法有两种重载形式 1 format String format Object args 该方法使用指定的格式字符串和参数返回一个格式化字符串 格式化后的新字符串使
  • 物联网安全的概念

    0x01 物联网安全的概念 随着各行业的迅速发展 物联网技术已是人们日常生活中必不可少的一部分 越来越多的设备开始加入IoT生态系统 越来越多未知设备的接入使安全问题成了物联网技术的最大关注点 大多数技术安全问题类似于常规服务器 工作站和智
  • RTMP协议封装H264和H265协议详解

    RTMP协议封装H264和H265协议详解 文章目录 RTMP协议封装H264和H265协议详解 1 RTMP和FLV 2 RTMP协议封装H264视频流 2 1 RTMP发送AVC sequence header 2 2 RTMP发送AV
  • linux开放指定端口命令

    方式一 CentOS 1 开启防火墙 systemctl start firewalld 2 开放指定端口 firewall cmd zone public add port 1935 tcp permanent 命令含义 zone 作用域
  • c++基础知识点-文件的创建,写入与读取(VS )

    用C 创建文件并且进行对文件的写入和读取操作 1 文件的创建 我用的软件是VS FILE fp1 fp2 errno t err err fopen s fp1 D privacy key txt wb if err 0 printf th
  • 到底什么是JS原型

    文章目录 到底什么是JS原型 一 首先大家在对JS原型进行解释的时候 会涉及两个概念 构造函数 原型对象 二 使用构造函数创建对象 三 与原型有关的几个方法 到底什么是JS原型 转载自blog 到底什么是JS原型 话说在前头 去网上查询很多
  • C++入门--类与对象(中)

    目录 一 类的6个默认成员函数 二 构造函数 1 默认构造 三 析构函数 四 拷贝构造函数 1 理解值传参发生拷贝构造 2 浅拷贝与深拷贝 3 拷贝构造函数典型调用场景 五 赋值运算符重载 1 gt gt 运算符重载 2 赋值运算符重载 3
  • [Orangepi 3 LTS]学习记录(四)

    本章内容基于官方手册 OrangePi 3 LTS H6 用户手册 v2 4 与自己实际操作撰写 前面几章实现了开发板的SSH登陆 WiFi自动连接 开机自动登陆 以及SDK的安装 本章主要写wiringOP库安装和USB接口测试 一 26
  • LSTM时间序列预测MATLAB代码模板(无需调试)

    多序列 http t csdn cn yfjoh 数据在评论区 导入自己的数据即可预测并画图 1 环境清理 clear clc close all 2 导入数据 单序列 D readmatrix B xlsx data D 2 要求行向量
  • 教大家如何破解某款返利机器人最新版1.4.7

    这次带来的是最新版1 4 7的破解视频教程 也是你们想要的视频教程 功能方面大家都知道的 我就不说了 废话不说 大家看视频吧 哪里不对之处 还请见谅 当然你有更好的破解方法 也可以分享下 OD自己搜索吧 用冷小黑大大的或者其他能过SE检测的
  • 2022软件测试3大发展趋势,看看你都知道吗?

    软件测试这个行业前景怎么样 小白入行的话会出现什么样的问题 遇到瓶颈怎么破 这一系列问题是很多在行业外观望的小白甚至是刚刚入行的萌新测试们都密切关注 毕竟这关系到自己未来的收入 甚至是决定自己的职业发展方向 那么下面就一一化解你们的疑问 现
  • CSS:使用nth-child()选择最后一行

    CSS 使用nth child 选择最后一行 常见问题 在容器中使用float布局一个列数固定行数不定的格子 大小都一样 展示区域 即格子的个数不定 我们该如何更优雅的处理边距 只有方块的两两之间才有边距 因为只有容器中最右一列和最下一行的
  • 弹性云服务器(ECS)结合 Docker 容器

    介绍 容器 Container 是一种轻量级的虚拟化技术 所谓的轻量级虚拟化 就是使用了一种操作系 统虚拟化技术 这种技术允许一个操作系统上用户空间被分割成几个独立的单元在内核中运行 彼此互不干扰 这样一个独立的空间 就被称之为一个容器 本
  • HTTP传输协议原理

    目录 1 简介 1 1 简单的HTTP协议 1 2 主要特点 1 3 HTTP请求响应模型 2 工作原理与过程 2 1 工作原理 2 2 用户访问网站的过程 2 3 HTTP协议栈中各层数据流 3 请求 1 请求方法 2 请求的网址 3 请
  • scala ide + helloworld

    http blog csdn net asongoficeandfire article details 21490101 简介 在上一篇文章中 我们阐述了Coursera使用Scala的理由 以及Scala的优缺点 说多不如少练 我们今天
  • 一文讲透缓存方案及常见问题——初篇

    Hello 大家好 今天跟大家聊的一个话题就是 缓存 目前 面向C端的服务架构中 除开管理后台等访问量很少 实时性要求较高的服务可不使用缓存外 缓存已成为高性能分布式系统里不可或缺的一环 本文不打算过多涉及具体的缓存组件如Memcached