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

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(使用前将#替换为@)

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

  • MySQL - 从临时表插入

    这看起来非常简单 但我坚持使用简单的插入语句 见下文 begin work CREATE TEMPORARY TABLE IF NOT EXISTS insert table AS select r resource id fr file
  • 如何在 MySQL 中求和时间?

    正如您在图片中看到的 我有一份停机报告 显示了所选工厂在选定日期的停机时间 现在我想添加所有的值 Time Duration 列并将其显示在附近的单独显示中 TOTAL TIME DURATION 例如 在图像中 所选日期为 2015 年
  • 每月获取记录,但如果该月没有记录,则为零

    如果我有以下 SQL 表 Tests id type receiveDate 1 Blood 2012 01 18 2 Blood 2012 01 20 3 Blood 2012 01 18 4 Blood 2012 03 01 5 Blo
  • 如果 Row1 = 值 1,则更新其他行

    我有一个小的 php 脚本 用于访问 mySql 数据库 我想在数据库中插入新记录之前查看该数字 值 1 是否等于数据库中的记录 这也在第 1 行 所以我想 查看传入的电话号码是否等于数据库中的电话号码 如果是这样 则必须保持电话号码相同的
  • 无法在 Mac 上启动 MySQL

    使用 Brew 安装后 我无法运行 MySQL 我使用的是 OS X El Capitan 版本 10 11 3 和 MySQL Server 版本 5 7 11 当我启动服务器时 我收到 启动 MySQL 错误 服务器退出而不更新 PID
  • MySQL 错误 1172 - 结果包含多行

    在存储过程中运行查询时 我从 MySQL 收到此错误 错误代码 1172 结果包含多行 我理解错误 我正在做一个SELECT INTO var list 因此查询需要返回单行 当我使用LIMIT 1 or SELECT DISTINCT 错
  • 非常大的字段会对 MySQL 数据库产生负面影响吗?

    我目前正在使用 Django 构建一个网站 并希望托管用户生物样式页面 该页面可能长达几 KB 这些字段不一定需要搜索 但在查找用户名时确实需要提供 将这些数据存储在数据库中会产生负面影响吗 如果我使用带有数据库链接的静态文本文件 我的服务
  • 如何从批量数据中的mysql列中删除所有非数字字符

    我想从列中删除所有非数字字符 我的数据库中有大量数据 目前我正在使用以下链接中描述的方法 http venerableagents wordpress com 2011 01 29 mysql numeric functions http
  • 让登录更安全

    我已使用此代码进行管理员登录 仅当用户输入正确的用户名和密码时才应打开loginhome php 但后来我意识到这根本不安全 任何人都可以直接访问 mywebsite loginhome php 而无需登录 注销后 可以使用后退按钮打开 l
  • 在 SQL 中,如何从 SELECT * FROM ... 中排除结果?

    我知道我的标题不太具有描述性 让我在这里详细解释一下 假设一个表有 26 个字段 例如字段 a 字段 z 我只想要一个选择查询只返回 15 个字段 所以 通常 我会执行 SELECT field a field b field o FROM
  • 将庞大数据库从亚马逊RDS导出到本地mysql

    我在 Amazon RDS 上有一个 mysql 数据库 大约 600GB 数据 我需要将其移回本地专用服务器 但我不知道从哪里开始 每次我尝试初始化 sqldump 时它都会冻结 有没有办法将其移至 S3 甚至可能在开始下载之前将其分成更
  • Mysql用in语句限制

    我正在写一个查询 SELECT user bookmarks id as user bookmark id bookmark id user bookmarks user id bookmark url bookmark website b
  • 获取带有计数的不同记录

    我有一张桌子personid and msg列 personid msg 1 msg1 2 msg2 2 msg3 3 msg4 1 msg2 我想得到总计msg对于每个personid 我正在尝试这个查询 select distinct
  • Redis 会话序列化器 3.2 和 4.2 之间不匹配

    我有一个基于 Spring Cloud 的应用程序在多个 spring boot 服务器上运行 所有服务器使用 EnableRedisHttpSession共享相同的Spring Session 我现在想将第三方小部件集成到我的应用程序中
  • posts_search 中的自定义查询

    如何使用此查询作为我的自定义搜索查询 add filter posts search my search is perfect 20 2 function my search is perfect search wp query sWord
  • mysql自动存储记录创建时间戳

    mysql 有什么方法可以在创建记录时自动将时间戳存储在记录行中 我试图使用时间戳 数据类型 和 current timestamp 作为默认值 但后来意识到每次更新记录时都会更新 我只需要一些可以存储创建时间戳的东西 Thanks Set
  • 从Django中具有外键关系的两个表中检索数据? [复制]

    这个问题在这里已经有答案了 This is my models py file from django db import models class Author models Model first name models CharFie
  • 在 android 中建立与 MySQL 的池连接

    我需要从我的 Android 应用程序访问 MySQL 数据库 现在所有的工作都通过 DriverManager getConnection url 等等 但我必须从多个线程访问数据库 所以我必须使用连接池 问题1 是 com mysql
  • mysql排序和排名语句

    我需要一些 mysql 语句的帮助 我的表 1 有 7 列 表 2 有 8 列 额外的列名为排名 我的语句应该是这样的 从表 1 中选择全部 然后按 用户数 排序 将其插入表 2 中并排名开始 1 2 3 等 table 1 usernam
  • 快速将列的副本添加到 MySQL 表

    我需要一种快速的方法来复制表中的 DATETIME 列并为其指定一个新名称 我的表中有一个名为 myDate 的列 名为 myResults 我需要一个查询来在名为 newDate 的表中创建一个新列 该列的数据与 myDate 列完全相同

随机推荐

  • 军工重组

    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 因此无论是处于实用还是成