关于Redis缓存一致性问题的优化和实践

目录标题

  • 导语
  • 正文
    • 分布式场景下无法做到强一致
    • 即使是达到最终一致性也很难
      • 缓存的一致性问题
      • 缓存是如何写入的
    • 如何感知数据库的变化
    • 最佳实践一:数据库变更后失效缓存
    • 最佳实践二:带版本写入
  • 总结与展望
    • 阿里XKV
    • 腾讯DCache

导语

Redis缓存一致性的问题是经常遇到的问题,关于redis的优化有很多种,今天给大家介绍的是得物电商中对redis的优化和实战,希望能给大家带来一些启迪。

正文

最近团队里我们在密集的讨论Redis缓存一致性相关的问题,电商核心的域如商品、营销、库存、订单等实际上在缓存的选择上各有特色,那么在这些差异的业务背后,我们有没有一些最佳实践可供参考呢?

本文尝试着来讨论这个问题,并给出一些建议。

在讨论之前,有两个重点我们需要达成一致:

分布式场景下无法做到强一致

  • 不同于CPU硬件缓存体系采用的MESI协议以及硬件的强时钟控制,分布式场景下我们无法做到缓存与底层数据库的强一致,即把缓存和数据库的数据变更做成一个原子操作。
  • 硬件工程师设计了内存屏障(Memory Barrier)的概念,提供给软件开发者不同的一致性选项在性能与一致性上进行权衡。

即使是达到最终一致性也很难

  • 分布式场景下,要做到最终一致性,就要求缓存中存储的是最新版本的数据(或者缓存为空),而且是在数据库更新后很迅速的就要达到这个一致性的状态,要做到是极其困难的。
  • 我们会面临硬件、软件、通信等等组件非常多的异常情况。

在这里插入图片描述

缓存的一致性问题

一般化来说,我们面临的是这样的一个问题,如下图所示,数据库的数据会有5次更新,产生6个版本,V1~V6,图中每个方框的长度代表这个版本持续的时间。

我们期望,在数据库中的数据变化后,缓存层需要尽快的感知到并作出反应,如下图所示,缓存层方框中的间隔代表这个时间段缓存数据不存在,V2、V3以及V5版本在缓存中不存在并不会破坏我们的最终一致性要求,只要数据库的最终版本和缓存的最终版本是相同的就可以了。

[图片链接]

缓存是如何写入的

缓存写入的代码通常情况下都是和缓存使用的代码放在一起的,包含4个步骤,如下图所示:W1读取缓存,W2判断缓存是否存在,W3组装缓存数据(这通常需要向数据库进行查询),W4写入缓存。

每一个步骤间可能会停顿多久是没有办法控制的,尤其是W3、W4之间的停顿最为要命,它很可能让我们将旧版本的数据写入到缓存中。

我们可能会想,W4步的写入,带上W2的假设,即使用WriteIfNotExists语义,会不会有所改善?

[图片链接]

考虑如下的情形,假设有3个缓存写入的并发执行,由于短时间数据库大量的更新,它们分别组装的是V1、V2、V3版本的数据。

使用WriteIfNotExists语义,其中必然有2个执行会失败,哪一个会成功根本无法保证。

我们无法简单的做决策,需要再次将缓存读取出来,然后判断是否我们即将写入的一样,如果一样那就很简单;如果不一样的话,我们有两种选择:

  • 将缓存删除,让后续别的请求来处理写入。
  • 使用缓存提供的原子操作,仅在我们的数据是较新版本时写入。

[图片链接]

如何感知数据库的变化

数据库的数据发生变化后,我们如何感知到并进行有效的缓存管理呢?

通常情况下有如下的3种做法:

  1. 使用代码执行流
    通常我们会在数据库操作完成后,执行一些缓存操作的代码。这种方式最大的问题是可靠性不高,应用重启、机器意外当机等情况都会导致后续的代码无法执行。

  2. 使用事务消息作为使用代码执行流的改进
    在数据库操作完成后发出事务消息,然后在消息的消费逻辑里执行缓存的管理操作。可靠性的问题就解决了,只是业务侧要为此增加事务消息的逻辑,以及运行成本。

  3. 使用数据变更日志
    数据库产品通常都支持在数据变更后产生变更日志,比如MySQL的binlog。可以让中间件团队写一款产品,在接收到变更后执行缓存的管理操作,比如阿里的精卫。可靠性有保证,同时还可以进行某个时间段变更日志的回放,功能就比较强大了。

最佳实践一:数据库变更后失效缓存

这是最常用和简单的方式,应该被作为首选的方案,整体的执行逻辑如下图所示:

[图片链接]

W4步使用最基本的put语义,这里的假设是写入较晚的请求往往也是携带的最新的数据,这在大多的情形下都是成立的。D1步使用监听DB binlog的方式来删除缓存,即前述使用数据变更日志中介绍的方法。

这个方案的缺点是:在数据库数据存在高并发更新且缓存读取流量较大的情况下,会有小概率存在缓存中存储的是旧版本数据的情况。

通常的解法有四种:

  1. 限制缓存有效时间:设定缓存的过期时间,比如15分钟。即表示我们最多接受缓存在15分钟的时间范围内是旧的。

  2. 小概率缓存重加载:根据流量比设定一定比例的缓存重加载,以保证大流量情况下的缓存数据的一致性。比如1%的比例,这同时还可以帮助数据库得到充分的预热。

  3. 结合业务特点:

    • 针对营销的场景:在商品详情页/确认订单页的优惠计算时使用缓存,而在下单时不使用缓存。这可以让极端情况发生时,不产生过大的业务损失。
    • 针对库存的场景:读取到旧版本的数据只是会在商品已售罄的情况下让多余的流量进入到下单而已,下单时的库存扣减是操作数据库的,所以不会有业务上的损失。
  4. 两次删除:D1步删除缓存的操作执行两次,且中间有一定的间隔,比如30秒。这两次动作的触发都是由“缓存管理组件”发起的,所以可以由它支持。

最佳实践二:带版本写入

针对商品信息缓存这种更新频率低、数据一致性要求较高且缓存读取流量很高的场景,通常会采用带版本更新的方式,整体的执行逻辑如下图如示:

[图片链接]

和“数据库变更后失效缓存”方案最大的差异在W4步和D1步,需要缓存层提供带版本写入的API,即仅当写入数据版本较新时可以写入成功,否则写入失败。

这同时也要求我们在数据库增加数据版本的信息。

这个方案的最终一致性效果比较好,仅在极端情况下(新版本写入后数据丢失了,后续旧版本的写入就会成功)存在缓存中存储的是旧版本数据的可能。

在D1步使用写入而不是使用删除可以极大程度的避免这个极端情况的出现,同时由于该方案适用于缓存读取流量很高的场景,还可以避免缓存被删除后W3步短时间大量请求穿透到DB。

总结与展望

对于缓存与数据库分离的场景,在结合了业界多家公司的实践经验以及ROI权衡之后,前述的两个最佳实践是被应用的最为广泛的,尤其是最佳实践一,应该作为我们日常应用的首选。同时,为了最大限度的避免每个最佳实践背后可能发生的不一致性问题,我们还需要切合业务的特点,在关键的场景上做一些保障一致性的设计(比如前述的营销在下单时使用数据库读而不是缓存读),这也显得尤为重要(毕竟如“背景”中所述,并不存在完美的技术方案)。

除了缓存与数据库分离的方案,还有两个业界已经应用的方案也值得我们借鉴:

阿里XKV

简单来讲就是在数据库上部署一个Memcache的Server,它直接绕过数据库层直接访问存储引擎层(如:InnoDB),同时使用KV client来进行数据的访问。它的特点是数据实际上与数据库是强一致的,性能可以比使用SQL访问数据库提升5~10倍。缺点也很明显,只能通过主键或者唯一键来访问数据(这只是相对SQL来说的,大多数缓存本来也就是KV访问协议)。

腾讯DCache

不用自行维护缓存与数据库两套存储,给开发人员统一的一套数据视图,由DCache在缓存更新后自行持久化数据。缺点是支持的数据结构有限(key-value,k-k-row,list,set,zset),未来也很难支持形如数据库表一样复杂的数据结构。

在这里插入图片描述

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

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

相关文章

023.PL-SQL进阶—视图

课 程 推 荐我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈虚 拟 环 境 搭 建 :&#x1…

SAP Fiori-Vscode 环境搭建中npm报错

文章目录 前提: vscode 安装好了,node 配置完毕,npm环境搭建OK新建一个Fiori 初始化初始化性项目的报错&警告Q1: npm WARN config global --global, --local are deprecated. Use --locationglobal insteadQ2: npm打包出现警告&#xff0…

代码随想录算法训练营第五十八天 | 拓扑排序精讲-软件构建

目录 软件构建 思路 拓扑排序的背景 拓扑排序的思路 模拟过程 判断有环 写代码 方法一: 拓扑排序 软件构建 题目链接:卡码网:117. 软件构建 文章讲解:代码随想录 某个大型软件项目的构建系统拥有 N 个文件,文…

jsp+sevlet+mysql实验室设备管理系统2.0

jspsevletmysql实验室设备管理系统2.0 一、系统介绍二、功能展示1.控制台2.申购设备3.设备列表4.设备维护5.设备类型6.报废设备7.维修记录 四、其它1.其他系统实现 一、系统介绍 系统主要功能: 普通用户:控制台、申购设备、设备列表、设备维护、设备类型…

用命令行的方式启动.netcore webapi

用命令行的方式启动.netcore web项目 进入指定的项目文件夹,比如我发布后的代码放在下面文件夹中 在此地址栏中输入“cmd”,打开命令提示符,进入到发布代码目录 命令行启动.netcore项目的命令为: dotnet 项目启动文件.dll --urls"ht…

会员计次卡渲染技术-—SAAS本地化及未来之窗行业应用跨平台架构

一、计次卡应用 1. 健身中心:会员购买一定次数的健身课程或使用健身房设施的权限。 2. 美容美发店:提供一定次数的理发、美容护理等服务。 3. 洗车店:车主购买若干次的洗车服务。 4. 儿童游乐场:家长为孩子购买固定次数的入场游…

Word使用手册

修改样式 编辑word文档时,标题和正文文本通常有不同的格式,如果能将这些格式保存为样式,下一次就能直接调用样式,而不需要重复手动设置格式。 可以将样式通常保存为不同的 样式模板.docx,要调用不同样式集&#xff0…

什么是科技与艺术相结合的异形创意圆形(饼/盘)LED显示屏

在当今数字化与创意并重的时代,科技与艺术的融合已成为推动社会进步与文化创新的重要力量。其中,晶锐创显异形创意圆形LED显示屏作为这一趋势下的杰出代表,不仅打破了传统显示设备的形态束缚,更以其独特的造型、卓越的显示效果和广…

【隐私保护】如何找出CLS方案的创新之处?

本文的写作动机是自己在写论文时对寻找创新点的一些思考,我想把寻找创新点的过程总结和概括成一套流程,从而变成一个思维工具,帮助我自己更好的看懂论文。由于本人水平有限,所以目前这套流程并不完善,这篇博文的作用自…

C++系列-STL中find相关的算法

STL中find相关的算法 💢find相关的算法💢💢find,find_if举例💢💢find_first_of举例💢💢find_end举例💢💢adjacent_find举例 秋词二首 刘禹锡刘禹锡〔唐代〕 …

使用python对股票市场进行数据挖掘的书籍资料有哪些

炒股自动化:申请官方API接口,散户也可以 python炒股自动化(0),申请券商API接口 python炒股自动化(1),量化交易接口区别 Python炒股自动化(2):获取…

golang学习笔记19——golang做服务发现与注册的深度剖析

推荐学习文档 golang应用级os框架,欢迎star基于golang开发的一款超有个性的旅游计划app经历golang实战大纲golang优秀开发常用开源库汇总golang学习笔记01——基本数据类型golang学习笔记02——gin框架及基本原理golang学习笔记03——gin框架的核心数据结构golang学…

[linux 驱动]内核定时器详解与实战

目录 1 描述 2 结构体 2.1 timer_list 3 相关函数 3.1 DEFINE_TIMER 3.2 add_timer 3.3 del_timer 3.4 msecs_to_jiffies 3.5 usecs_to_jiffies 5 示例 4 延迟工作队列delayed_work 4.1 结构体 4.1.1 delayed_work 4.2 相关函数 4.2.1 DECLARE_DELAYED_WORK 4.2…

Grafana面板-linux主机详情(使用标签过滤主机监控)

1. 采集器添加labels标签区分业务项目 targets添加labels (模板中使用的project标签) … targets: [‘xxxx:9100’] labels: project: app2targets: [‘xxxx:9100’] labels: project: app1 … 2. grafana面板套用 21902 模板 演示

Python 错误 ValueError 解析,实际错误实例详解 (一)

文章目录 前言Python 中错误 ValueError: No JSON object Could Be Decoded在 Python 中解码 JSON 对象将 JSON 字符串解码为 Python 对象将 Python 对象编码为 JSON 字符串Python 中错误 ValueError: Unsupported Pickle Protocol: 3Python 中的 Pickling 和 UnpicklingPython…

微信小程序原生支持TS、LESS、SASS能力探究

文章目录 原生支持开始使用旧项目新建项目TS声明文件更新 功能说明less 使用全局变量sass 使用全局变量 可以参考原文 在之前开发小程序中,无法使用 less/sass 等 css 预编译语言,也无法使用 TS 进行开发,但在最新的编辑器版本中&#xff0c…

Vue3:el-table实现日期的格式化

后端如果返回的是时间戳,需要我们进行日期格式化 例如:2024-09-11T14:19:14 定义一个日期解析的工具组件 export function formatDateAsYYYYMMDDHHMMSS(dateStr: any) {const date new Date(dateStr);const year date.getFullYear();const month S…

Android 12 SystemUI下拉状态栏禁止QuickQSPanel展开

1.概述 遇到需求,QuickQSPanel首次下拉后展示快捷功能模块以后就是显示QuickQSPanel,而不展开QSPanel,接下来要从下滑手势下拉出状态栏分析功能实现。也就是直接是展开状态。 2、涉及核心类 frameworks\base\packages\SystemUI\src\com\and…

高级实时通信:基于 Python 的 WebSocket 实现与异步推送解决方案

高级实时通信:基于 Python 的 WebSocket 实现与异步推送解决方案 目录 🟢 WebSocket 协议概述🔵 在 FastAPI 中实现 WebSocket🟣 Django Channels 实现异步实时通信🔴 使用 Redis 实现实时推送 🟢 1. WebS…

关于 Camera Tuning 岗位的一些认识和看法

前言: 之前也写过几篇关于Camera Tuning岗位的相关介绍: IQ Tuning 学习路线 关于入职 ISP Tuning 岗位的几个问题及解答 随着工作时间越长,对这个岗位的认识较之前也有了一些更深刻的理解,这里整理总结一下。 一方面是当做现阶…