缓存Caffeine之W-TinyLFU淘汰测录

news2024/9/21 1:44:11

我们常见的缓存是基于内存的缓存,但是单机的内存是有限的,不能让缓存数据撑爆内存,所有需要缓存淘汰机制。https://mp.csdn.net/editor/html/115872837 中大概说明了LRU的缓存淘汰机制,以及基于LRU的著名实现guava cache。除了LRU淘汰策略外,其是常见的还有FIFO以及LFU,只是说目前用的最多的是LRU。

LRU

LRU记录了缓存中数据项的访问时间,在缓存数据量达到一定成都的时候,淘汰掉最远访问的那些数据项。

LRU的理论基础就是局部性原理,认为最近访问过的数据,在未来还会继续访问的概率是比较大的,所以当缓存满的时候,首先将那些很久没有访问过的数据项给淘汰出缓存。

优点:实现简单(链表+hash表),且命中率还是相对比较高的。所以LRU也是当前使用的最多的缓存淘汰算法。

更多更具体的介绍可参考:LRU算法及其优化策略——算法篇 - 掘金

缺点:对于偶发的批量操作支持不友好,偶发的批量操作会降低缓存命中率。这种批量操作会将非热点数据打量加载到内存,按照LRU可能就将真正的热点数据挤出缓存。

mysql的优化

LRU算法中,对批量支持不友好的本质原因其实还是LRU算法淘汰的依据仅仅是访问时间,缺少数据项的访问次数的统计,当需要淘汰的时候,只能根据访问时间来进行淘汰,所以就会导致偶尔的一次批量操作将访问频率低的数据项读入到缓存。所以解决这个问题的办法就是加入对数据项访问量的统计,在淘汰策略的时候,将访问频率也加入到决策因素中。

mysql将整个LRU分成了两段来避免这个问题,为每个数据项增加了一个统计时间窗口来解决这个问题(这个其本质就是增加了统计访问频率),如下图:

  1. 缓存淘汰的位置是从old读队尾开始淘汰的,即当缓存不够的时候,优先将old端队尾位置的元素剔除缓存

  1. 当数据读入的时候,是将该数据项放到了old的队头。 然后给这个数据项一个统计时间窗口,即innodb_old_blocks_time参数指定的时间段,只有在指定时间窗口内,这个数据项再次被访问到的时候,就会将这个数据项挪到yong端。

这样,热点数据都是放在了yong端,优先淘汰的数据都是在old端。偶发的批量操作读取的数据都在old,会优先于yong端数据被淘汰,从而避免批量操作将热点数据淘汰出缓存,降低命中率。

LFU

LFU为缓存中的每个数据项都维护了一个计数器,会统计每个缓存项的访问次数,当缓存使用量达到一定程度的时候,会优先淘汰那些访问次数比较小的数据项

LFU是基于这种思想进行设计:一定时期内被访问次数最少的数据项,在将来被访问到的几率也是最小的。具体可参考LFU算法及其优化策略——算法篇 - 掘金

优点:很明显,LFU是没有LRU偶发批量操作导致的将热点数据挤出缓存的问题。

但是LFU的缺点也很明显:

  1. 对于那种大热门而又迅速冷却的数据,比较难以淘汰出缓存。比如某明星出轨消息,当刚曝出时,浏览量特别高,可能段时间内就是上千万次的读取,但是第二天就没人关心这个信息了,那几乎就没什么读取量了,但是因为第一天太热门了,远远超过了其他数据项的读取次数

  1. 因为淘汰是基于数据项的访问次数的,那么对于新加入到缓存的数据,更容易被淘汰,除了偶发的批量操作导致的新加入到缓存的数据以外,更多的情况可能是新加入到缓存的数据被后续操作访问的可能性是比较大的。这种数据被提出缓存,就有可能造成很快又被加载进来的。

  1. 因为LFU需要为每个数据项维护访问频率的数据,一方面实现复杂度相对就较高了,而且会额外占用空间。另外一个问题就是,这个访问次数如果是有史以来的访问次数和,那其实根据这个访问次数实现的淘汰策略必定不是很理想,所以这个访问次数一定是最近一段时间的访问次数,这就有个问题了,最近一段时间是多长时间?以及到期的时候这些访问次数的清理其实都是比较麻烦的事情。

不管是FIFO、LRU还是LFU,解决了一定问题,但是还是会有其他的问题出现,归根接地,其原因无外乎是以为淘汰策略参考的因素单一:FIFO只是参考了进入队列时间、LRU只是参考了最近访问时间、LFU只是参考了访问次数。所以都会有些问题。

所以在实际生产中,一个缓存框架的淘汰策略,实际上很少会单独使用某一个指标来决定淘汰策略,比如mysql的缓存,虽然整体上是LRU,但是实际上也引入了访问次数的因素。所以往往都是将多个维度相结合,共同来决定应该淘汰缓存转给你的哪些数据,达到既不会将缓存撑爆,又能够维持一个比较搞的缓存命中率,使得缓存效果最大化。

W-TinyLFU

这个淘汰算法其实就是结合LRU和LFU算法的优缺点的一个缓存淘汰算法,说白了就是要保留LRU和LFU的这些优点(淘汰策略要结合最近访问时间和访问次数),并规避掉上述各自的问题。

参考:W-TinyLFU论文阅读与原理分析

TinyLFU

要为缓存中的每个数据项维护一个计数器,最基本的结构那就是一个HashMap,key=缓存项的key,value就是这个缓存项的访问计数。这个结构实现基本功能是没问题的,但是问题在于效率和空间上:key是数据项的key,会占据额外的空间的,另外每次访问缓存都需要修改对应数据项的计数器,这里就会有并发问题,且如果当hash有冲突,那么这个访问效率就会直线下降,导致缓存的访问不稳定。对于一个高效的缓存来说,这都是致命的问题。

所以就需要找到一个减少空间占用的方式:那就是压缩,但需要考虑怎么从压缩后的数据里获得对应key的频率。对于使用HashMap的场景中,如果需要空间考虑,那么可以参考存在性判断的思路:使用BitMap,但是BitMap的问题就是hash冲突,解决它的方式就是布隆过滤器,用多个hash函数来减少hash冲突,然后容忍小概率的hash冲突导致的不准确(这也是为啥说boomfilter对于不存在判断一定是准的,但是存在判断存在误差的原因)。

Count-Min Sketch也是采用了类似布隆过滤的方式来为数据项维护计数,和布隆过滤器的区别在于,布隆过滤器的值,只需要一个bit就足够用来表示是否存在在了,但是Count-Min Sketch,对应的值需要是一个计数值,就不能是一个bit了,采用4bit来进行计数。

Caffeine的实现中认为,一个数据项的访问频率到了15就算是很高了,就属于热点数据了,所以采用4bit来存储计数就已经够了。当计数值达到15的时候,就将技术值减半,这也起到了一个衰减的作用,达到计数器记录的是"最近"访问次数的效果,从而也解决了那种热极一时的数据不容易淘汰的问题。

为了减少hash冲突的问题,Caffeine使用了4个hash函数,为每个数据项保存了4个计数器,即一个数据项需要4*4=16bit来存储访问频率,而在使用淘汰的时候,取的是这4个计数值中的最小值来进行访问频率比较;只要一个计数器的值达到15,则中4个计数值都将减半。

Caffeine的TinyLFU的实现在FrequencySketch中,

获取一个数据项的技术统计频率,是取四个计数器中的最小值,这也是为啥叫TinyLFU的原因:

数据项的访问次数+1

Caffeine是用一个long数组来保存数据项的访问计数的,一个long有64位,可以充当16个计数器,但其实要通过位运算完全利用上这64,java提供的能力还有有点困难,所以Caffeine取了数据项key的hash值的后左移两位,然后加上hash四次,可以利用到16个中的13个,利用率挺高的,或许有更好的算法能将16个都利用到。

ps:FrequencySketch里大量使用了位运算,代码阅读其实很不直观,可以先了解原理,然后结合debug、注释看。

W-TinyLFU

Count-Min Sketch算法解决了LFU中频率统计的问题,但是对于新加入数据更容易被淘汰、热极一时的数据项更难被淘汰出去的问题还存在,而这些问题正式LRU不会存在的。所以W-TinyLFU就是在TinyLFU前面增加了一个LRU缓存。它将缓存分成了两大部分:Window cache和main cache,不同区域采用不同的淘汰策略

  • window cache:这部分是一个LRU缓存。当读入缓存的时候,都是先进入到window cache;当window cache满的时候,按照LRU淘汰出数据项,对于从window cache淘汰出的数据项,再根据TinyLFU来决策是否进入到main cache中。在Caffine的实现中,window cache默认是整个cache的1%,缓存数据是通过一个ConcurrentHashMap来实现,而window cache的LRU策略是通过一个双端队列:accessOrderEdenDeque来实现的。

  • main cache:进一步划分为两部分

  • protected部分:在Caffine的实现中,这部分默认占main cache的80%,其淘汰策略的实现是通过一个双端队列accessOrderProtectedDeque来实现的。这里面存放的其实是访问频率比较高的热数据,这是整个缓存的核心部分,是高缓存命中的保证。

  • probation部分:在Caffine的实现中,这部分默认占main cache的20%,其淘汰策略的实现是通过一个双端队列accessOrderProbationDeque来实现的。里面存放的其实就是访问频率比较少的冷数据,即即将别淘汰出缓存的数据。

Caffeine的整体实现在BoundedLocalCache中(源码分析的可参考http://events.jianshu.io/p/77e90f79f075,Caffeine的使用可参考https://www.chinacion.cn/article/5629.html,缓存的概要介绍可参考https://my.oschina.net/u/4072299/blog/3019442)

从整个实现上可以发现,在思路上和mysql对lru的优化都是打通小异的,都是将整个LRU缓存进行分区,然后在实际执行淘汰的时候加上数据项的访问次数。

  • 对于mysql的优化,对于偶发的批量操作数据全都在old端,当整体缓存不够的时候会被优先淘汰,真正的热数据在yong端,yong端不够用了,就会按照LRU淘汰到old,old的数据如果再次被频繁访问又会被放到yong端,从而达更高的缓存命中。

  • caffeine其实是类似的,新加载进来的数据首先会进入到window cache,包括偶发的批量操作的数据项,而真正将数据项淘汰出缓存是从probation,所以新加入的数据项,只会等到window cache满的时候,才会去比较频率,而这个时候新加入的数据项可能访问次数又变高了,在跟probation淘汰出来的数据项比较频率的时候,最终淘汰掉的是probation中的数据项,即window cache出来的数据项访问频率并不高,也不一定会被淘汰,因为有个访问次数小于5,是随机淘汰的机制。所以window cache就解决了LFU新加入数据项访问频率低容易被淘汰的问题。

tinyLFU解决了数据项频率统计占用空间的问题,而且计数器满自动筛检一半的机制又避免了热极一时的数据项不容易被淘汰的问题。

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

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

相关文章

Python学习笔记——类(面向对象)

Python中使用类(class〕来实现面向对象编程。Python中的类, 具有面向对象编程的所有基本特征:允许多继承、派生类可以重写它父类的任何方法、方法可以调用父类中同名的方法, 对象可以包含任意数量和类型的数据成员。创建类Python中, 使用class语句来创建…

Python 第7章 文件与数据格式化 笔记1

编码:print(云.encode(utf8))print(b\xe4\xba\x91.decode(utf8))要注意代码的编码方式。7.1文件概述windows中一个文件的完标识:D:\Downloads\新建文本文档.txt依次是路径,文件名主干,拓展名。没有包含除了文本字符以外的其他数据…

【JavaEE初阶】第四节.多线程基础篇 Thread类的使用、线程的几个重要操作和状态

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 一、Thread类的常见构造方法 二、Thread 的几个常见属性 三、和线程相关的几个重要的操作 3.1 启动线程 - start() 3.2 中断线程 3.3 等待线程 - join() …

JDK 8新特性之基本发展史

目录 一:Java SE的发展历史 二:Open JDK来源 三:Open JDK 和 Oracle JDK的关系 四:Open JDK 官网介绍 小结 : 一:Java SE的发展历史 Sun公司在1991年成立了一个称为绿色计划( Green Project )的项目&a…

Ubuntu22.04 美化

一:安装软件 sudo apt install gnome-tweaks chrome-gnome-shell sudo apt install gtk2-engines-murrine gtk2-engines-pixbuf sudo apt install sassc optipng inkscape libcanberra-gtk-module libglib2.0-dev libxml2-utils 二:安装GNOME扩展插件…

Windows 卸载 Visual Studio Code、MinGW-w64、CMake

文章目录1.卸载 Visual Studio Code1.1 在控制面板中找到 Visual Studio Code 将其卸载1.2 删除之前安装过的插件1.3 删除用户信息和缓存信息2.卸载 MinGW-w642.1 删除之前解压出来的文件夹2.2 删除之前配置过的环境变量3.卸载 CMake3.1 删除之前解压出来的文件夹3.2 删除之前配…

无 Hadoop 环境部署 Kylin4

1相比于 Kylin 3.x,Kylin 4.0 实现了全新 spark 构建引擎和 parquet 存储,使 kylin 不依赖 hadoop 环境部署成为可能。无Hadoop环境也降低了运维成本,减少了系统资源占用。 以下操作基于centos7.6 单机版本 部署版本信息如下 JDK 1.8Hive …

【Javascript】面向对象编程,this,原型与原型链,类与实例,class,实现Map,stack,Queue ,Set

❤️ Author: 老九 ☕️ 个人博客:老九的CSDN博客 🙏 个人名言:不可控之事 乐观面对 😍 系列专栏: 文章目录对象中的方法/thisthis使用bind函数原型原型链类与实例classclass语法补充Map实现Map实现stack实…

【国产GD32芯片解析中科微北斗+GPS模块经纬度数据详细教程-附完整代码工程】

国产GD32芯片解析中科微北斗GPS模块经纬度数据详细教程-附完整代码工程简介准备工作PC端需要用到的工具代码下载地址GD32F103C8T6最小系统板代码实现GD32串口引脚定义如下:串口的初始化串口0初始化代码:串口1初始化代码串口的输入串口0的输入代码如下&am…

非标准包 game.rgss3a 的打开方法 | 2023 年实测

写在前面:最近在玩 RPG 游戏,想拆一个 Game.rgss3a 包,在网上找了很久的拆包方法,感觉写的比较凌乱,我来给大家整理一下吧。不过我本人的技术能力也很差,不确定说的是不是对的,就当是给大家提供…

中国智造助推跨境电商企业迈向全球市场

现今,跨境电商行业发展的如火如荼,中国智造也在不断助推跨境电商企业迈向全球市场。业内人员在新常态下的思想也有了一些改变,现在的跨境电商都是“平台物流”,在物流环节,也需要我们的专业团队去进行整合,…

GD32F450寄存器和库函数

GD32F4xx用户手册 GD32F450xx数据手册 GD32F3x0固件库使用指南 一、寄存器介绍 1. 存储器映射表 GD32是一个32位的单片机,它的地址范围为2的32次方,也就是4GB的地址空间。 为了降低不同客户在相同应用时的软件复杂度,存储映射是按Corte…

python能做的100件事03-python爬虫

文章目录1. scrapy介绍2 新建爬虫项目3 新建蜘蛛文件4 运行爬虫5 爬取内容5.1分析网页结构5.2 关于Xpath解析5.3 接着解析电影数据5.4 下载缩略图5.5 完整代码6 最后说明本例基于python3和scrapy爬虫框架,不再介绍python的基础知识和爬虫的基本知识。1. scrapy介绍 …

制药企业的发展趋势--行业公司数据调研

制药行业是国家重点培育发展的战略产业。制药行业的发展对人民健康、医药科技和社会进步等方面都有着十分重要的作用。下面笔者将阐述近年来制药行业发展的现状及趋势,并对制药行业的研发、销售等多维度的信息进行展示与解读。中国制药企业现状目前,中国…

分享103个PHP源码,总有一款适合您

PHP源码 分享103个PHP源码,总有一款适合您 下面是文件的名字,我放了一些图片,文章里不是所有的图主要是放不下..., 103个PHP源码下载链接:https://pan.baidu.com/s/1_T5IzwgcntFuyqulehbSzQ?pwdv6ds 提取码&#…

vue-router 使用与原理分析

简介 Vue Router 是Vue.js的官方路由。与Vue.js核心深度集成&#xff0c;让用Vue.js构建单页应用&#xff08;SPA&#xff09;变得更加简单。 使用 创建 1、在安装好Vue Router依赖后&#xff0c;在App.vue中引入router-view&#xff0c;它是渲染的容器 <div id"…

ARM X210开发板的软开关按键问题

一、X210 开发板的软启动电路详解 《x210bv3.pdf》 (1) 210 供电需要的电压比较稳定&#xff0c;而外部适配器的输出电压不一定那么稳定&#xff0c;因此板载了一个文稳压器件 MP1482. 这个稳压芯片的作用就是外部适配器电压在一定范围内变化时稳压芯片的输出电压都是 5V。 (2)…

Redis 主从复制

目录一、简介二、复制功能三、将服务器设置为从服务器3.1、手动设置3.2、REPLICAOF 配置选项3.3、取消复制四、查看服务器的角色4.1、查看主服务器4.2、查看从服务器五、其他配置5.1、无硬盘复制5.2、降低数据不一致情况出现的概率5.3、可写的从服务器5.4、选择性命令传播六、w…

SpringBoot统一功能处理

目录 一、统一用户的登录验证 1.1 Spring AOP 实现用户统一登录验证 1.2Spring拦截器实现统一用户的登录验证 1.3 实例演示&#xff08;通过url访问controller层的方法&#xff09; 二、统一异常处理 三、统一数据格式返回 3.1 统一数据返回格式的优点 3.2 统一数据返回…

旺季到来,跨境电商卖家年末冲刺!

又是一年年末时&#xff0c;随着新年的到来&#xff0c;在年底这段时间里&#xff0c;对于跨境电商卖家来说&#xff0c;又是一个关键节点。而现在&#xff0c;卖家们也将迎来一年一度的旺季收官&#xff0c;在此过程中卖家需要做好哪些准备做好年末冲刺呢&#xff1f; 在许多…