缓存-基础知识

熟悉计算机基础的同学们都知道,服务的存储大多是多层级的,呈现金字塔类型。通常来说本机存储比通过网络通信的外部存储更快(现在也不一定了,因为网络传输速度很快,至少可以比一些过时的本地存储设备速度快)。常见的本机存储和网络设备有:

  1. 本机:从快到慢有CPU寄存器、CPU多级缓存、内存、磁盘
  2. 网络设备:从快到慢有Redis这类的基于内存的缓存,MySQL这类持久化数据库,RPC调用(如果假设RPC调用的下游也需要调用缓存和数据库等组件)等

那么使用缓存有什么好处呢?通俗地讲就是“快”,准确地说是读取快,每秒承载的访问次数多,这样服务端每秒能处理的请求就更多,服务总体的吞吐量就大。具体来说,基于内存的存储比基于磁盘的快,所以大家常常使用Redis作为MySQL的缓存;而本机的内存缓存又会比通过网络调用Redis快,因此现在一些互联网公司也在搭建本地缓存(Local Cache)来缓解Redis的压力。

另外需要看到缓存的缺点:缓存增加了数据的副本份数,所以写入的份数也增加了,写入性能必然降低,一致性管理成本必然增加。所以缓存基本都是用于读多写少,且不要求强一致性的场景。

更新事件捕获与更新缓存的策略

在常见的在线服务调用中,我们会构建缓存来避免对数据库和下游的调用,来减少数据库压力和对下游服务的压力。当相关数据改变时,可以使缓存失效或者更新缓存。那么这里引入了两个问题,第一个是如何知道缓存该被更新了,第二个是怎么选择删除缓存和更新缓存的策略。

更新事件捕获

通常有两种方式来捕获更新事件:

  1. 服务代码主动更新:用户操作涉及修改数据时,处理用户请求的服务代码会刷新缓存和数据库里的数据
  2. 订阅数据库binlog延迟更新:数据库配置binlog,并通过一些binlog同步工具将数据库变更记录发送到消息队列。服务端订阅消息队列的指定话题获取变更记录,进而更新缓存数据

1#通常被用于更新自己团队的数据,而2#通常用于更新下游服务,或者作为缓存更新失败时的兜底逻辑。毕竟下游是数据的生产方,让下游更新缓存的时候通知所有上游不太现实,这种基于订阅模式的事件捕获机制有很强的扩展性。

更新策略选择

具体策略(删除或更新)的选择通常取决于数据是否在本次处理中已经计算好了。比方说收到一条变更消息,说用户的年龄已经被更新了。与年龄相关的可能还有用户对好友的可见性、用户是否可以接收其它用户的信息等内容,它们都可能随着年龄的更改而同步变化,而这些信息目前还是未知的——除非再次请求下游。因此对于这种情况,我们会将相关的数据直接删除。由于年龄的更新频率非常低,所以我们可以认为删除相关缓存的操作对其性能的总体影响很小。

但是如果需要更新的字段比较独立,不会影响别的字段,比方说用户昵称或者头像,那直接更新对应的数据即可

本地缓存(Local Cache)

说完了更新缓存的策略,我们再来看看最近业界比较流行的Local Cache的实现。这里的local,指的是服务器的单实例,也就是每个实例都会有自己的缓存副本。它的适用条件是读请求次数远远大于写,且数据总量较小。比如对于Redis来说,性能指标的表现就是内存利用率低,但是CPU占用率很高。这对于现在的微服务架构来说比较常见,每个服务需要管理的字段不多,但是请求量巨大,比如对于社交应用来说,用户年龄相关的字段就符合这种访问模式,每次请求基本都要读取年龄字段,但是数据量却很小。

读写过程

本地缓存的读取策略很简单,不过就是在Redis之前加了个读取本地缓存的过程,如果缓存不存在就从更高层级缓存拉取。

那如何更新缓存呢?由于分布式系统的特性,每个服务器实例都有自己的内存,除非使用特别的路由策略,让同一个用户的请求只打到同一台机器上,否则更新缓存是很头疼的事情:用户的缓存可能存在于不同机器上,如何让多台服务器的缓存同时失效?因此在分布式的世界中,使用Redis这样的集中缓存比较常见,只要存储是集中式的且从计算单元中分离出来,服务就可以做成无状态的,减少数据状态的管理成本。

回忆一下我们刚刚说过的捕获更新和缓存更新方式,其实本地缓存的更新也不出其右:用binlog+消息队列同步数据库变更,所有启用本地缓存的服务器实例,都需要监听消息队列里的变更消息,并刷新(或者删除)本地缓存。唯一的不同是,如果需要刷新Redis缓存,那我们可以把刷新缓存的服务和处理用户请求的服务独立开来;但是本地缓存的缓存更新逻辑得和服务器逻辑都放在同一个实例中。这也很好理解,Redis是集中式的缓存,而本地缓存和服务器实例牢牢捆绑在一起。

本地缓存的优势

之前说过,本地缓存适用于读远远大于写,且请求量大但是缓存数据总量较小的场景。为什么呢?因为假如CPU和内存利用率都很高,即使本地缓存降低了Redis的读取次数,那也只能降低Redis的CPU占用率,而内存利用率还是很高。内存利用率和缓存数据总量有关,所以多一层缓存不会减少缓存的数据总量。根据木桶原理,即使CPU利用率降低了,由于内存的需求还在,也不能缩减实例的数量,所以并不能节约太多钱(可以降低一下CPU规格,但是一台生产环境实例的基本价格在那,降低或提高规格带来的费用变化并不大)。

那有人问了,本地缓存不是也提高了服务端的内存占用,这方面的费用不算了吗?刚刚说过,生产环境里一台实例的基础费用不低,而且增加一些内存带来的费用相比于减少Redis实例数量来说很小。而且本来本地缓存就适合数据总量小的场景,因此增加一些内存容量(比如4-8G)就能有客观的命中率(比如50%+)。

我相信还有人会问,增加了更新链路不是也带来了额外费用吗?这就要再强调一下本地缓存的适用场景:读远远大于写,比如大了两个数量级。这样缓存的更新频率足够低,缓存更新链路所需的资源也足够少。

缓存常见问题

缓存如何解决热点问题

热点问题是缓存遇到的常见问题之一,比如强如Redis也有其吞吐量上限,而Redis同一个key只能存储在一个分片、一个进程里,可能还是无法应付一些热点用户的数据访问(比如某个热点问题,某个热点主播)。《数据密集型系统》给出两个比较笼统的方案:

  1. 给热key实例更多资源:比如将其单独存储在一个实例中,或者给它分配更好的机器。但是这种方案很容易就会达到上限,因为不具备水平扩展能力。
  2. 拆key:比如某个用户(id为1234)是热点,那就将它的数据存在100个副本中,每个副本的key是id后面加上两位数。这样当看到1234这个id时,服务端会自动在后面加上两位随机数,生成形如123401,123402…123499这样的id,再到Redis里访问。这相当于把一个热key的访问压力分散为原来的1/100。这种方式明显是优化了读请求。如果是写请求比较重,那也可以做类似的事情,将写分散到不同的实例上,但读的时候就需要读取所有的100个分片。

由于1#不具备水平扩展能力,所以一些业界流行的做法都采用了类似2#的方式,只不过实现起来各有特点,比方说在Redis之前加一层缓存代理,这层代理也是一个分布式服务,一旦有热key存在,就会将热key缓存到代理服务的本地缓存里,实现了类似于2#中拆key的效果(因为代理服务也是分布式的,多个实例都可以缓存热key的内容,进而分散了Redis单实例的压力)

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

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

相关文章

报表工具DevExpress .NET Reports v25.1新版本亮点:AI驱动的扩展

DevExpress Reporting是.NET Framework下功能完善的报表平台,它附带了易于使用的Visual Studio报表设计器和丰富的报表控件集,包括数据透视表、图表,因此您可以构建无与伦比、信息清晰的报表。 DevExpress Reporting控件日前正式发布了v25.1…

kubernetes中pod的管理及优化

目录 2 资源管理方式 2.1 命令式对象管理 2.2 资源类型 2.2.1 常用的资源类型 2.2.2 kubectl常见命令操作 2.3 基本命令示例 2.4 运行和调试命令示例 2.5 高级命令示例 3 pod简介 3.1 创建自主式pod(生产环境不推荐) 3.1.1 优缺点 3.1.2 创建…

解释一下,Linux,shell,Vmware,Ubuntu,以及Linux命令和shell命令的区别

Linux 操作系统概述Linux 是一种开源的类 Unix 操作系统内核,由 Linus Torvalds 于 1991 年首次发布。作为现代计算的基础设施之一,它具有以下核心特征:多用户多任务特性允许多个用户同时操作系统资源,而模块化设计使其能够适应从…

Windows 系统中,添加打印机主要有以下几种方式

在 Windows 系统中,添加打印机主要有以下几种方式,我将从最简单到最复杂为您详细介绍。 方法一:自动安装(推荐首选) 这是 Windows 10 和 Windows 11 中最简单、最现代的方法。系统会自动搜索网络(包括无线和有线网络)上可用的打印机并安装驱动程序。 操作步骤: 进入…

Mixture of Experts Guided by Gaussian Splatters Matters

Mixture of Experts Guided by Gaussian Splatters Matters: A new Approach to Weakly-Supervised Video Anomaly Detection ICCV2025 https://arxiv.org/pdf/2508.06318 https://github.com/snehashismajhi/GS-MoEAbstract 视频异常检测(VAD)是一项具有…

SeaTunnel Databend Sink Connector CDC 功能实现详解

Databend 是一个面向分析型工作负载优化的 OLAP 数据库,采用列式存储架构。在处理 CDC(Change Data Capture,变更数据捕获)场景时,如果直接执行单条的 UPDATE 和 DELETE 操作,会严重影响性能,无…

算法230. 二叉搜索树中第 K 小的元素

题目:给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 小的元素(从 1 开始计数)。示例 1:输入:root [3,1,4,null,2], k 1 输出:1 示例 2&#xff1…

Seaborn数据可视化实战:Seaborn多变量图表绘制高级教程

Seaborn多变量图表实战:从数据到洞察 学习目标 本课程将带领学员深入了解Seaborn库中用于绘制多变量图表的高级功能,包括联合图(Joint Plot)、对角线图(Pair Plot)等。通过本课程的学习,学员将能…

【数智化人物展】首衡科技CTO李蒙:算法会过时,数据会贬值,只有系统智能才具未来性

李蒙本文由首衡科技CTO李蒙投递并参与由数智猿数据猿上海大数据联盟共同推出的《2025中国数智化转型升级先锋人物》榜单/奖项评选。大数据产业创新服务媒体——聚焦数据 改变商业“算法会过时,数据会贬值。”当我第一次在内部战略会上抛出这句话时,现场…

word——将其中一页变成横向

在word中如何将其中一页变成横向? 在需要横向的这一页和上一页插入分节符(连续) 1.点击布局→分隔符→分节符(连续) 2.在所需要横向页将纸张方向改为横向即可。

使用WORD实现论文格式的样式化制作【标题样式、自动序列、页号(分节)、自动目录(修改字体类型)】

背景 每家院校对论文的格式都有一系列的特定要求,相应的会有一份格式标准的说明文档,该说明文档中会罗列对文档各个项的格式标准要求(例如:题目、1级标题、2级标题、页号、每个级别的字体字号,行距,段前段…

分享一个免费开源的网站跟踪分析工具Open-Web-Analytics(和GoogleAnalytics一样)

做独立网站的福音,这个是免费开源的,可增改性强。 开源地址:https://github.com/Open-Web-Analytics/Open-Web-Analytics 下载源码包 接着下载PHP工具:我用XP小皮 phpstudy_pro 地址:phpStudy - Windows 一键部署 …

Maxscript如何清理3dMax场景?

在3ds Max的创作过程中,随着项目的推进,场景往往会积累许多冗余元素,如孤立帮助对象、隐藏对象以及空层等,它们不仅让场景显得杂乱无章,还会占用资源、降低视口性能,影响工作效率。别担心,在本教程中,我们将为大家带来实用妙招——通过简单的Maxscript脚本片段,快速清…

JavaScript 性能优化实战:从分析到落地的全指南

一、引言:为什么 JS 性能优化至关重要?用户体验的直接影响:加载慢、交互卡顿如何流失用户(引用 Google 研究:页面加载延迟 1 秒,转化率下降 7%)业务价值关联:性能优化对 SEO、留存率…

线性回归学习笔记

一、线性回归简介1. 核心定义线性回归是一种通过属性的线性组合进行预测的线性模型,核心目标是找到一条直线(二维)、一个平面(三维)或更高维的超平面,使模型的预测值与真实值之间的误差最小化。2. 适用场景…

Kotlin 中适用集合数据的高阶函数(forEach、map、filter、groupBy、fold、sortedBy)

在 Kotlin 中,高级函数(Higher-Order Functions)是一个非常强大的特性。高级函数是指可以将函数作为参数传递,或者将函数作为返回值返回的函数。这种特性使得代码更加灵活和可复用。 使用高级函数可以方便地对集合进行操作,如 map、filter、reduce 等。 在事件驱动的编程中…

Redis 哈希表的核心——`dictEntry` 结构体

接上一篇 Redis 哈希表的本质:数组里存的是什么 Redis 哈希表的核心——dictEntry 结构体,是真正承载我们存储的键值对数据的那个结构。 它的定义非常简洁,但设计得很巧妙。以下是其 C 语言代码(在 Redis 源码 src/dict.h 中&a…

Jsqlparser + Freemarker + Vue3 数据透视报表设计方案

1. 目标与前置条件目标:基于 JSQLParser FreeMarker Vue3 构建一套“可配置的数据透视报表”能力,实现从任意基础 SQL/视图出发,按维度/指标灵活聚合、筛选、排序、分页、导出,并支持钻取、联动、TopN、同比环比等常见分析操作。…

SpringBoot3 Ruoyi芋道管理后台vben5.0

新技术栈(Vue3、Vite6、TypeScript、SpringBoot3/SpringCloud基于Vben5.0最新版本,全面采用Vue3 Vite6 Ant Design Vue TypeScript技术栈,并同时支持SpringBoot3单体架构与SpringCloud微服务架构前端技术栈:Vue3 Vite6 TS A…

K8S - NetworkPolicy的使用

1 前置条件2 控制范围3 隔离类型4 如何识别5 主要字段6 案例演示 前置条件 网络策略通过网络插件来实现。 要使用网络策略,你必须使用支持 NetworkPolicy 的网络解决方案。 创建一个 NetworkPolicy 资源对象而没有控制器来使它生效的话,是没有任何作用的…