Caffeine Cache解析(一):接口设计与TinyLFU

devtools/2024/10/17 20:05:00/

Caffeine is a high performance Java caching library providing a near optimal hit rate.

  • 自动加载value, 支持异步加载
  • 基于size的eviction:frequency and recency
  • 基于时间的过期策略:last access or last write
  • 异步更新value
  • key支持weak reference
  • value支持weak reference和soft reference
  • 淘汰(或移除)通知
  • 缓存获取数据统计

Caffeine的性能benchmark可参考Caffeine Benchmarks,
可以看到Caffeine在不同读写比的情况下, 吞吐量比Guava Cache和Jdk内置的HashMap都有较大提升。
接下来了解下Caffeine的使用、接口设计和TinyLFU简介。

使用

java">        // manual cache,Cache-Aside模式Cache<String, String> cache = Caffeine.newBuilder().expireAfterWrite(Duration.ofHours(1)).build();// null表示不存在key@Nullable String val = cache.getIfPresent("hello");if (val == null) {cache.put("hello", "world");}// key不存在则走后面的loading functionString val2 = cache.get("hello", key -> "world");// loading cache,Read-Through模式LoadingCache<String, String> loadingCache = Caffeine.newBuilder().expireAfterAccess(Duration.ofMinutes(10)).maximumSize(2000).build(new CacheLoader<String, String>() {@Overridepublic @Nullable String load(@NonNull String s) throws Exception {// your loading logicreturn "";}});// 不存在会自动loadingString val3 = loadingCache.get("hello");

接口设计

CacheCache按不同维度划分:

  • kv是否有限:Bounded or Unbounded,判断参见方法com.github.benmanes.caffeine.cache.Caffeine#isBounded,大部分场景都使用BoundedCache
  • 是否自动loading: ManualCache和LoadingCache
  • 是否异步: Cache和AsyncCache

对应接口UML如下(实现类只画出了BoundedCache),其接口和类均位于com.github.benmanes.caffeine.cache包下:

可以看到,核心的kv存储是放在了LocalCache接口(本质是一个ConcurrentMap)下,而其他的Loading和Async接口在存储基础上附加了自动加载value和异步加载的能力,以同步接口为例,接口能力如下:

  • Cache: 提供缓存基础能力,包含设置、读取、失效、获取缓存统计数据等功能;
  • LoadingCache: 提供自动加载能力,在Cache基础上新增不存在则loading value的读取以及refresh方法;
  • LocalCache: 提供核心存储能力,线程安全和原子能力保证,继承自java.util.concurrent.ConcurrentMap
  • LocalManualCache: 基于LocalCache做存储的非自动Loading Cache;
  • LocalLoadingCache: 继承自LocalManualCache,新增Loading功能。

关于以上接口的一个关键抽象实现类BoundedLocalCache,其作用解释如下:

This class performs a best-effort bounding of a ConcurrentHashMap using a page-replacement algorithm to determine which entries to evict when the capacity is exceeded.

TinyLFU & W-TinyLFU

在看BoundedLocalCache类前先来了解下TinyLFU & W-TinyLFU算法,
算法出自论文:TinyLFU: A Highly Efficient Cache Admission Policy

  • admission policy:控制一个元素是否能进入缓存
  • eviction policy:当缓存满了时选择哪一个元素剔除缓存

论文中提出的TinyLFU指的是admission policy,
TinyLFU可以和 LRU 、SLRU 的eviction policy组合。

比如 论文中使用W-TinyLFU + LRU + SLRU的组合进行实验:

Window Tiny LFU (W-TinyLFU) … consists of two cache areas.
The main cache employs the SLRU eviction policy and TinyLFU admission policy
while the window cache employs an LRU eviction policy without any admission policy.

可以看到 W-TinyLFU 的main cache area 使用 TinyLFU 作为 admission policy。

TinyLFU 在 LFU的基础上 加了Tiny,而Tiny体现在counter计数(和frequency正相关) 的记录上。
通常大部分缓存的元素访问频率都会很小,如果使用integer来记录counter计数,会导致空间浪费,因此
TinyLFU 借助 approximate counting scheme 来记录counter计数,其原理和bloom filter类似,而 schema的具体实现有:

  • Counting Bloom Filter
  • CM-Sketch : Caffeine中使用该实现,参见类com.github.benmanes.caffeine.cache.FrequencySketch,论文可参考:An Improved Data Stream Summary: The Count-Min Sketch and its Applications

Caffeine中的CM-Sketch使用4bit记录counter次数,最大只能到15,
并使用long[]类型存储,一个元素可存储16个计数器,long[]即相当于论文中的二维数组。

此外还增加了一个DoorKeeper即一个Bloom Filter来优先存储counter计数为1的元素,大于1的才会进入CM-Sketch结构中。

此外,TinyLFU利用Freshness Mechanism保证TinyLFU计数器记录的次数不会无限制扩大:

Every time we add an item to the approximation sketch, we increment a counter.
Once this counter reaches the sample size (W), we divide it and all other counters in the
approximation sketch by 2.

对应的可以在FrequencySketch类的increment方法中找到reset方法的调用:

java">// This class maintains a 4-bit CountMinSketch.
// The maximum frequency of an element is limited to 15 (4-bits) 
// and an aging process periodically halves the popularity of all elements.
final class FrequencySketch<E> {...int sampleSize;int size;public void ensureCapacity(@NonNegative long maximumSize) {...sampleSize = (maximumSize == 0) ? 10 : (10 * maximum);if (sampleSize <= 0) {sampleSize = Integer.MAX_VALUE;}size = 0;}// 计数器次数小于15时才会增加计数器public void increment(@NonNull E e) {...boolean added = ...if (added && (++size == sampleSize)) {reset();}}...
}

http://www.ppmy.cn/devtools/126547.html

相关文章

如何安装和使用 Git Large File Storage (LFS)

在现代软件开发中&#xff0c;我们经常需要处理大型文件&#xff0c;如图像、音频、视频或二进制文件。Git 在处理这些大文件时可能会遇到性能问题&#xff0c;因为 Git 会存储文件的每一个版本。为了解决这个问题&#xff0c;Git Large File Storage (LFS) 应运而生。Git LFS …

【排序】——2.快速排序法(含优化)

快速排序法 递归法 霍尔版本(左右指针法) 1.思路 1、选出一个key&#xff0c;一般是最左边或是最右边的。 2、定义一个begin和一个end&#xff0c;begin从左向右走&#xff0c;end从右向左走。&#xff08;需要注意的是&#xff1a;若选择最左边的数据作为key&#xff0c;则…

适合女生的热门行业 女生上大学十大热门专业推荐

篇1:适合女生的热门行业 女生上大学十大热门专业推荐 1、高校老师 很多在校的学生,一直对教师这个职业不怎么看好。觉得工资低,一辈子就在一个地方了,发展缓慢。其实这个想法在你毕业后不久就会改变的。很多毕业去外面的人都很想很想再回到学校。 在高校当老师,压力不大…

【力扣 | SQL题 | 每日4题】力扣1164,3293,1308,1270

4 mid&#xff0c;四题都比较简单&#xff0c;没什么难度。 1. 力扣1164&#xff1a;指定日期的产品价格 1.1 题目&#xff1a; 产品数据表: Products ------------------------ | Column Name | Type | ------------------------ | product_id | int | | new_p…

Flythings学习(四)串口通信

文章目录 1 串口编程基本步骤1.1 打开串口1.2 配置串口 1.3 读串口1.4 发送串口1.5 关闭串口 2 综合使用3 如何在软件上保证串口稳定通信4 flythings中的串口通讯5 协议接收部分使用和修改方法6 通讯协议数据怎么和UI控件对接 1 串口编程基本步骤 串口通信有5个步骤 1.打开串口…

制造企业上云桌面需要考虑那些因素?

在江苏这片充满活力的经济热土上&#xff0c;制造业作为传统优势产业&#xff0c;正经历着前所未有的数字化转型浪潮。随着云计算技术的日益成熟和普及&#xff0c;越来越多的江苏制造企业开始将目光投向云桌面&#xff0c;以期通过这一创新技术实现降本增效、提升管理水平和增…

自动驾驶系列—自动驾驶整体开放平台:如何加速无人驾驶技术的落地?

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

ansible————ansible的文件管理

一、ansible文件管理常用的模块 file模块&#xff1a;创建文件/目录&#xff0c;删除/目录文件等 copy模块&#xff1a;将控制节点的文件送到被管理主机上 lineinfile模块&#xff1a;向文件输入内容 stat模块&#xff1a;显示文件的状态信息 fetch模块&#xff1a;从被管理…