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

2023-11-04

前文有提到,缓存其中一种实施方式是利用硬件的读取速度的差异来做缓存加速,但是更高速的存储介质往往受限于成本(价格贵)或硬件限制(CPU缓存物理大小),其容量相较硬盘要小很多。

再加上根据二八原则,热点数据可认为只占20%,因此无论是处于实用还是成本考量,缓存容量都会比持久化的存储容量小很多,这就带来了一个问题:当缓存容量使用完时,再有新数据尝试写入缓存,应当如何处理?这就涉及到缓存的淘汰算法了。

缓存淘汰算法

swap

swap严格来说并不算淘汰数据,只是作为一个知识扩展点介绍一下。swap是操作系统层的一种策略:在物理内存不足时,使用磁盘的一部分区域当做内存使用。当要使用到被swap到磁盘上的数据时,再把数据读取到内存。

当然我们知道硬盘IO是比较耗时的操作,尤其相较内存的速率来说,因此swap会严重影响性能,再加上我们使用缓存其实就是想加速访问磁盘,所以缓存系统正常来说不会主动使用这个策略。但如果缓存宿主机的物理内存不足,可能会引发这个策略,算是缓存异常的一种考虑场景。

LRU(Least Recently Used)

这个应该是耳熟能详的一种淘汰算法了,个人最早了解是在学习操作系统原理的时候。其名为最近最少使用,实际策略就是淘汰最久未使用的数据。

Java里的 LinkedHashMap就提供了LRU的简单实现,下面我们用一个链表来简单说明。

head              tail ↓                  ↓ A -> B -> C -> D -> E

假设缓存容量为5, 此时数据按照上图排列,LRU的策略是:如果有新数据插入,会将新数据插入到头部,同时淘汰掉尾部数据;如果访问的数据已有,将其移动到头部,如下所示:

(访问已有数据D之后的情况)head              tail↓                   ↓D -> A -> B -> C -> E


(插入新数据F之后的情况)
head              tail↓                   ↓F -> A -> B -> C -> D

LRU算法,简单易理解,其设计依据就是被访问的数据,很可能再次被访问。LRU在很多场景都有实际的应用,但有一个较大的缺陷:大范围扫描数据时,缓存命中率会急剧下降

假如我要做数据全量扫描,可以想象,LRU算法下,缓存内数据会被快速扫描到的数据替换掉,此时真正业务要读取的热点数据都无法命中缓存,只能重新从磁盘加载数据,而且很可能加载到缓存后,后又被扫描的数据顶替。

这个过程会持续到扫描完成,业务重新加载好对应的热点数据为止。

理论上使用LRU算法管理缓存时,如果有进行大范围扫描数据时,服务响应会剧烈抖动,这种价值较小的数据加载到缓存里,称之为缓存污染。在MySQL和Redis里,我们可以学习到大佬们是如何解决缓存污染的:

MySQL的策略:双段链表

MySQL作为持久化的关系型数据库,也使用了内存缓存来加速数据的读取,因此也需要考虑到内存的淘汰策略。其针LRU算法的缺陷,改进了扫描大量数据的情况下热点数据淘汰的问题。具体做法如下:

young                   old       tail↓                        ↓         ↓A -> B -> C -> D -> E -> F -> G -> H

MySQL将LRU链表分成两部分,young和old,实际上还在同一个链表处,但是有两个指针young和old。其具体算法策略如下:

1. young和old的长度为5:32. young、old 区域和LRU算法一样3. 新缓存数据只能存放在old处,不能直接进入young区域4. 如果old中的数据再次访问,并且距离上次访问已过去指定的时间(默认1s),
则允许移动到young头部

这个策略就是为数据库的大范围扫描量身定制:young区域就是有价值的热点数据区域,old区域则是做一个缓冲:当有大范围数据扫描时,数据会短暂停留在old区域并且不停地被新数据替换,真正的热点数据并不受影响,从而保证了业务查询的缓存命中率。

Redis的策略:LFU(Least Frequently Used)

LFU即最少使用频率,这个算法里数据的权重,不是访问时间的先后,而是数据的访问次数,当次数一样时,才像LRU一样对比访问时间。

具体来说,Redis内部在LRU的基础上,为每个数据还增加了访问计数器,淘汰时,访问次数少的数据会优先淘汰。当然Redis为了节约存储空间,只用了8bit的空间(最大值255)存储访问次数,如果运行一段时间后,大量的数据都访问了255次以上,这个算法就退化为LRU算法了,为了让次数不那么快地达到上限,Redis采用了概率增加的方案:即每次访问发生时,计算一个概率值,如果概率通过则加1,否则不变,这个增长的概率也可以通过配置调整,这个设计也非常有意思。

需要说明的是,Redis提供了多种数据淘汰策略供配置选择,LFU只是其中一种,用户可以按需选择符合业务的淘汰策略。

分布式缓存

上面的内容基本上都是在讲单机或者将缓存作为一个整体黑盒来讨论,接下来就来简单地讨论一下分布式缓存,其实分布式缓存也就是多个单体缓存通过一定的协作方式组成缓存集群,来保证缓存服务的高可用及支撑高性能的缓存服务。

数据分片

由于现如今业务的不断发展和摩尔定律的逐渐失效,单体的缓存无论是性能、容量可用性等方面都很难满足很多业务的需要。更何况像Redis这样的典型缓存,在大容量下也会引入一些新问题:例如故障恢复时间较长,fork操作阻塞时间过长等等问题,因此引入缓存集群,将数据分布到多个缓存节点就是一个更优的方案了。

而且由于缓存服务是存储数据,属于有状态的服务,不能像无状态的请求负载均衡器一样,采用随机分发或者顺序等分发策略。对于一份数据我们只能到特定的节点上去获取,而这是典型的哈希分布算法。

哈希算法,针对缓存的key取hashCode, 用hashCode对节点总个数取模,得到该数据所在节点,例如哈希值为93,缓存集群有5个,则93 % 5 = 3,去标号为3的节点取数据 。

但这种策略有个很大的问题,就是节点数量一旦变化,所有数据的分布可能都会重新调整。也即,调整节点数导致缓存全部失效,引发雪崩。为了解决这个问题,比较流行的办法是采用的是一致性Hash算法。

一致性Hash算法

普通的Hash算法,是按服务节点分配哈希槽,哈希槽和服务节点一一对应。一致性Hash算法,哈希槽和节点是多对一的关系,通过预先分配大量的哈希槽(例如2的32次方个槽)组成环形,然后再配置这些槽对节点的映射关系。
如下图所示:

图中由2的32次方个点组成的圆环上,每一个点是一个哈希槽,然后我们假设有A,B,C,D四个服务节点,我们将这些节点 映射在如图所示的位置上。我们可以约定,每个哈希槽的所属数据节点是顺时针查找到的第一个节点,因此图中K1, K2的数据应当去B节点上读写。

根据这个算法,当我们减少一个节点时,仅有归属到这个节点的数据会迁移到其下一个节点,如下图:

当B节点下掉之后,原本归属在B节点的K1,K2数据顺势迁移到了下一个节点C上。这样,就把减少节点的数据迁移控制在了一个较小的范围,即:原A~B这段范围的数据。增加一个节点的情况也容易推论,当我们节点数较多时,增减节点的数据影响范围就很小。

这样,通过增加一层哈希槽到数据节点的映射,将增减节点的影响由全量失效,优化为了部分失效。这就是一致性hash的优势所在。但是,一致性hash也有其缺陷:雪崩效应数据倾斜

雪崩效应则是由于节点失效后,数据转移至下一节点,造成下一个节点承受双倍数据量和访问量,可能造成下一个节点的崩溃,这样,再下一个节点将承担三倍的压力,从而全部宕机。当然,高可用架构下每个节点还会配备从库,宕机的情况下可以通过从库选主来恢复服务,从而减小雪崩发生的概率。

数据倾斜不算一致性hash特有的问题,就算是普通集群也会有这个问题,而在一致性hash里,解决这个问题的思路,是再加一层映射层:哈希环上某个槽顺时针找到的节点仍旧是虚拟节点,虚拟节点再映射到实际节点。这样,我们可以虚拟多个节点数据出来,通过配置虚拟节点到实际节点的映射关系,甚至是动态调整映射关系来维护节点负载的均衡。读者可以自行推导一下这个情况。

写在结尾

那么至此,缓存相关的知识点暂时介绍完成了。需要补充说明的是,任何方案都会有其优势及缺陷,即使我们在引入业界通用的技术方案时,也不应只盯着其优点,也需要考虑这个方案的缺陷及负面问题。

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

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

  • 如何在函数内部使用 require_once [重复]

    这个问题在这里已经有答案了 你好 我想在函数内使用 require once 但不起作用 实际上我的页面中有三个函数我该怎么做 它在外面工作但不在函数内部 请问有谁吗 这是我的代码
  • 使用 MySQL 触发器将所有表更改记录到辅助表

    我有一张桌子 CREATE TABLE data table data id INT NOT NULL AUTO INCREMENT PRIMARY KEY field1 INT NOT NULL field2 INT NOT NULL f
  • MySQL 可以存储多少行?

    所以我是一个初学者 刚刚自学了几个月的MySQL 我在工作中总是使用 phpMyAdmin 我过去的工作只涉及大约 100k 行的表 所以没有什么大问题 然而 我的客户现在想要在表中存储大约 800 万行 MySQL phpMyAdmin
  • 多人/单人测验游戏的数据库设计

    我在这里看到了很多问题 但没有人适合我的问题 我正在尝试创建一个可扩展的 ER 模型 如果我想添加更多数据 则不会破坏几乎任何东西 所以我尝试创建的是 有两种类型的用户 比如说管理员和工作人员 他们有不同的角色 管理员可以对问题进行 CRU
  • 如何更改Linux服务器中的MySQL表名不区分大小写?

    我正在开发一个旧网站 该网站曾经托管在 Apple 服务器上 当它迁移到新的 Linux 服务器时 它停止工作 我很确定这是因为 php 脚本中使用的所有 MySQL 查询对于表名都有不同的大小写组合 我不知道为什么原始开发人员在创建表名或
  • Session_set_save_handler 未设置

    我在设置 session set save handler 时遇到问题 我将 php ini 配置为 session handler user 这个简单的测试失败了 Define custom session handler if sess
  • PHP - While/Else 错误? [关闭]

    这个问题不太可能对任何未来的访客有帮助 它只与一个较小的地理区域 一个特定的时间点或一个非常狭窄的情况相关 通常不适用于全世界的互联网受众 为了帮助使这个问题更广泛地适用 访问帮助中心 help reopen questions 我有以下
  • MySQL 启动错误 - 根元素丢失

    我在 Windows Server 2003 R2 上安装 MySQL 大约两个月了 启动时 我们会看到一个错误 显示 高严重性错误 根元素丢失 然后是另一个高严重性错误 显示 在调用 WriteToLog 方法之前必须定义日志文件路径 任
  • Sails 嵌套模型集合

    我有 3 个型号 用户模型 module exports schema true attributes login type string required true hosts collection host via owners acc
  • 是否可以使用 LOAD DATA INFILE 类型命令来更新数据库中的行?

    伪表 primary key first name last name date of birth 1 John Smith 07 04 1982 眼下名包含多行的用户全名 期望的结果是分割数据 因此first name包含 John la
  • mysql 准备好的语句错误:MySQLSyntaxErrorException

    我使用准备好的语句编写了选择语句 每次尝试运行都会出现此错误 我如何克服这个错误 我的jdbc连接器是mysql connector java 5 1 13 bin jar 我的代码 public Main add ad to getAdD
  • InnoDB如何存储字符列?

    这个问题仅解决 短 的问题CHAR and VARCHAR列存储在 InnoDB 表中 Does a CHAR 10 列正好占用 10 个字节吗 尾随空格会发生什么情况 对于每个字符需要超过 1 个字节的字符集怎么办 如何VARCHAR 1
  • Elastic Beanstalk 上的 Django + MySQL - 查询 MySQL 时出错

    当我在 Elastic beanstalk 上托管的 Django 应用程序上查询 MySQL 时 出现错误 错误说 admin login 处出现操作错误 1045 用户 adminDB 172 30 23 5 的访问被拒绝 使用密码 Y
  • 如何从 mysql 数据库中提取数据并使用 D3.JS 进行可视化?

    我有一个数据库MySQL我想在其中可视化D3 JS 为了做到这一点 首先我想parse中的数据JSON格式 然后编写一个基本代码 从数据库中提取数据并使用D3 JS 我环顾四周 但找不到我想要的东西 因为我是新手D3 JS 我怎样才能做到这
  • PDO 和 MySQL 全文搜索

    我正在将所有站点代码从使用 mysql 函数转换为 PDO 关于 PDO 的 PHP 文档对于我的需求来说并不清楚 它为您提供了可以使用的功能 但没有详细解释它们在不同场景下的情况 基本上 我有一个 mysql 全文搜索 sql SELEC
  • 对于 IN 列表中的缺失值返回 NULL

    我有一个这样的表 id val 1 abc 2 def 5 xyz 6 foo 8 bar 和一个像这样的查询 SELECT id val FROM tab WHERE id IN 1 2 3 4 5 返回 id val 1 abc 2 d
  • MySQL 更新具有多个值的查询

    我在数据库中有一个表 其记录如下 match id guess result 125 1 0 130 5 0 233 11 0 125 2 0 我的用户为每场比赛选择一个猜测 我有一个函数可以根据比赛的结果计算猜测的结果 如果猜测正确 结果
  • 如何在我的查询中使用日期格式?

    这适用于 phpmyadmin 但是当我在代码上使用时给我一个错误 错误说 解析错误 语法错误 意外的 我的语法有什么问题 gt
  • 当我在 PHP 中将 print_r() 应用于数组时,为什么会得到“Resource id #4”? [复制]

    这个问题在这里已经有答案了 可能的重复 我如何从 PHP 中的 MySql 响应中 回显 资源 id 6 https stackoverflow com questions 4290108 how do i echo a resource
  • 计算 MySQL 中的行数以及实际行内容

    MySQL 中有没有办法执行单个 SQL 语句来返回所选行以及结果行数 我可以做这个 SELECT COUNT FROM BigTable WHERE firstname LIKE a 这给了我一个带有计数 37 781 的结果行 我可以像

随机推荐

  • 军工重组

    http bar stockstar com p8448439 1 html 下周可千万别洗出来 2660到现在用了没多久就临近3000点 只要地产一起来马上就到3600了 地产现在不涨并不是不想涨 而是只要地产一起来马上就到3600 多数
  • VSCode安装教程最新,包教包会!

    一 VScode下载 1 进入VScode官网 官网地址 https code visualstudio com 点击 Download 进入下载 不要点击 Download for Windows Stable Build 否则它会自动帮
  • 编译Linux内核生成Image和System.map文件

    p span style font family 华文楷体 font size 12pt background color rgb 255 255 255 一直想琢磨琢磨Linux内核 便开始看 Linux内核完全注释 可是发现一头雾水 所
  • 用java实现计算器四则运算以及混合运算

    贴代码 本例测试是基于junit eclipse可安装对应 的java包 我用的是idea 添加插件即可 import java io BufferedReader import java io IOException import jav
  • eclipse 配置 C++

    前言 最近有项目需要c 但是c 自从离校那时就没碰过了 所以要重新学习下 因为曾经为了做自己的博客网站 学了java 下载了eclipse 也是在eclipse上写的博客网站的 所以对eclipse还是相对熟悉的 而且平时写代码都是用vim
  • android手势识别opencv,较为成熟的安卓项目--人面识别,手势识别向

    一 人脸识别 1 目标检测 目标追踪 人脸检测 人脸识别 效果 2 Android下使用OpenCV实现人脸检测 效果 3 人脸标识 效果 4 人脸检测 github https github com VernonVan Face 效果 主
  • Jmeter 中随机函数__Random 的使用

    前段时间 在做接口测试时 经常遇到接口参数需要输入不同的内容或者手机号码等 不允许输入重复的参数内容 比如不同的手机号码 那此时可以通过Random 随机函数来解决此问题 以前的文章有介绍过使用time函数来实现 详见 http blog
  • RuntimeError: Error(s) in loading state_dict for Net(让人心累的错误)

    RuntimeError Error s in loading state dict for Net size mismatch for classifier 4 weight xxxxxx 后面一堆错误 这个是model py 千万千万别
  • 【DL】第 6 章:语言建模

    大家好 我是Sonhhxg 柒 希望你看完之后 能对你有所帮助 不足请指正 共同学习交流 个人主页 Sonhhxg 柒的博客 CSDN博客 欢迎各位 点赞 收藏 留言 系列专栏 机器学习 ML 自然语言处理 NLP 深度学习 DL fore
  • 小程序跳转:云开发之h5跳小程序

    目录 前言 前提条件 注意 实现步骤 更多前端知识 前言 此方案是我在实际开发中的全部过程 因为我也是第一次做小程序的云开发 一开始根据这个文档就遇到了一些坑 所以在这里我做了更详细的步骤分解 非个人主体并且已认证的 微信认证 小程序 使用
  • tictoc例子理解10-12

    tictoc10 12 tictoc 10 几个模块连接 发送消息直到模块3收到消息 tictoc 11 新增信道定义 tictoc 12 双向连接信息简化定义 tictoc 10 几个模块连接 发送消息直到模块3收到消息 让我们用几个 n
  • 【机器学习】 Matlab 实现多种分类器(感知机、KNN、Logistic、最大熵、决策树、朴素贝叶斯)的二分类

    写在之前 这是本人的统计学习方法作业之一 老师要求一定要用Matlab编程 本人在此之前未曾大量使用Matlab 因此某些算法可能因为不知道函数或者包而走了弯路 代码高亮查了一下 没找到Matlab的所以用了C的 部分算法参考了某些算法的p
  • github中fork分支和pullrequest的最佳实践

    github中fork分支和pullrequest的最佳实践 github中fork分支和pullrequest的最佳实践 最近在参与一个国外的github开源项目 遇到自己fork了源库 一段时间之后 源库已经更新了一些内容 这样 自己f
  • 【uni-app】使用uni-app实现简单的登录注册功能

    文章目录 前言 一 页面布局 二 注册页面 1 注册接口使用 2 注册成功提示 3 注册成功页面跳转 4 完整代码 三 登录页面 1 登录接口使用 2 本地存储使用 3 完整代码 总结 前言 大家好 今天和大家分享一下如何在uni app中
  • Vue3(快速上手)

    Vue2 与 Vue3 的区别 数据双向数据绑定 Vue2 0 数据绑定 是通过 Object defineProperty 来劫持对象属性的 geter 和 seter 操作 当数据发生改变发出通知 Object defineProper
  • 简单工厂模式、工厂方法模式和抽象工厂模式之间的异同

    注 纯属个人理解 如有错误请大家指正 相同之处 AbstractProduct ap Factroy createClass 1 都是利用工厂类 工厂子类 来创建对应的类对abastractProduct进行实例化操作 不同之处 简单工厂模
  • 循环-13. 求特殊方程的正整数解(15)

    本题要求对任意给定的正整数N 求方程X2 Y2 N的全部正整数解 输入格式 输入在一行中给出正整数N lt 10000 输出格式 输出方程X2 Y2 N的全部正整数解 其中X lt Y 每组解占1行 两数字间以1空格分隔 按X的递增顺序输出
  • Comparable接口对list的多条件排序

    版权声明 本文为博主原创文章 未经博主允许不得转载 https blog csdn net xiaoyanghapi article details 52496325 普通的类要实现排序 必须实现Comparable接口 并重写Compar
  • python生成exe文件运行闪退解决方法

    python生成exe文件运行闪退解决方法 使用pyinstaller生成 exe文件 pyinstaller F filename py 用python写了一个程序 在python下运行是正常的 但是生成exe文件后运行闪退 我当时怀疑是
  • 一文讲透缓存方案及常见问题——进阶篇

    前文有提到 缓存其中一种实施方式是利用硬件的读取速度的差异来做缓存加速 但是更高速的存储介质往往受限于成本 价格贵 或硬件限制 CPU缓存物理大小 其容量相较硬盘要小很多 再加上根据二八原则 热点数据可认为只占20 因此无论是处于实用还是成