网站首页 > 技术文章 正文
SpringBoot集成Caffeine
Caffeine 因使用了 Window TinyLfu 回收策略,提供了一个近乎最佳的命中率。
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
因为使用代码比较简单,这里不再过多粘贴代码,有兴趣的可以去查看。
https://gitee.com/lizhifu/tomato-cloud/tree/master/tomato-study/tomato-study-caffeine
W-TinyLFU 算法
三种算法比较:
- FIFO:先进先出,先进入缓存的会先被淘汰,会导致命中率很低。
- LRU:最近最少使用,每次访问数据都会将其放在我们的队尾,如果需要淘汰数据,就只需要淘汰队首即可。
问题:例如有个缓存 key 在 1 分钟访问了 10000次,再后 1 分钟没有访问这个数据,此时其他的数据访问,那么这个缓存 key 就有可能会被淘汰。并且如果偶然性的要对全量数据进行遍历,那么“历史访问记录”就会被刷走,造成污染。
- LFU:最近最少频率使用,使用额外的空间记录每个缓存 key 的使用频率,然后选出频率最低进行淘汰。这样就避免了 LRU 不能处理时间段的问题。
问题:需要使用额外的存储空间;对突发性的稀疏流量无力,因为前期经常访问的记录已经占用了缓存,偶然的流量不太可能会被保留下来,例如秒杀,这些访问量较大,但是秒杀结束后就没有了使用价值,此时缓存会一直存在。
总结:W-TinyLFU就是结合LFU和LRU,LFU用来应对大多数场景,LRU用来处理突发流量。
Count-Min Sketch 算法
Count-Min Sketch 记录我们的访问频率,原理跟 Bloom Filter 一样,只不过 Bloom Filter 只有 0 和 1 的值,那么你可以把 Count–Min Sketch 看作是“数值”版的 Bloom Filter。
基本的思路:
- 创建一个长度为 x 的数组,用来计数,初始化每个元素的计数值为 0;
- 对于一个新来的元素,哈希到 0 到 x 之间的一个数,比如哈希值为 i,作为数组的位置索引;
- 这是,数组对应的位置索引 i 的计数值加 1;
- 那么,这时要查询某个元素出现的频率,只要简单的返回这个元素哈希望后对应的数组的位置索引的计数值即可。
类似于一个二维数组intwidth,depth表示4种不同的hash算法,如果只用一个hash算法,那么两个key产生hash冲突的概率是0.1,那么四个hash冲突的概率则为0.1的四次方。所以从四个hash算法中找到对应的数值,取最小的数值则为这个key的实际频率。
Caffeine 实现Count-Min Sketch 算法
Caffeine 对这个算法的实现在FrequencySketch。
- Caffeine 中采用了一个long类型的一维数组进行记录频率,数组的大小为最接近的2的幂的数。
- Caffeine 中规定频率最大为15,15的二进制位1111,总共是4位。
- Caffeine 只用了四种hash算法,每个Long型被分为四段,每段里面保存的是四个算法的频率。这样做的好处是可以进一步减少Hash冲突。等于是在原有基础上 X4。
缓存访问频率计算思路:
- 计算缓存 key 所在 4 个段的哪个段内。
- 对缓存 key 4 次计算long数组的位置。
- 疑频率最大为15,超过进行全局除以2衰减。
保新(衰减机制)
为了让缓存保持“新鲜”,剔除掉过往频率很高但之后不经常的缓存,Caffeine 有一个新鲜度机制。就是当整体的统计计数达到某一个值时,那么所有记录的频率统计除以 2。比如size等于100,如果他全局提升了size*10也就是1000次就会全局除以2衰减,衰减之后也可以继续增加。
// size变量就是所有记录的频率统计之,即每个记录加1,这个size都会加1
// sampleSize一个阈值,从FrequencySketch初始化可以看到它的值为maximumSize的10倍
if (added && (++size == sampleSize)) {
reset();
}
void reset() {
int count = 0;
for (int i = 0; i < table.length; i++) {
count += Long.bitCount(table[i] & ONE_MASK);
table[i] = (table[i] >>> 1) & RESET_MASK;
}
size = (size >>> 1) - (count >>> 2);
}
部分源码注释
FrequencySketch的一些属性:
// 种子数
static final long[] SEED = { // A mixture of seeds from FNV-1a, CityHash, and Murmur3
0xc3a5c85c97cb3127L, 0xb492b66fbe98f273L, 0x9ae16a3b2f90404fL, 0xcbf29ce484222325L};
static final long RESET_MASK = 0x7777777777777777L;
static final long ONE_MASK = 0x1111111111111111L;
int sampleSize;
// 为了快速根据hash值得到table的index值的掩码
// table的长度size一般为2的n次方,而tableMask为size-1,这样就可以通过&操作来模拟取余操作
int tableMask;
// 存储数据的一维long数组
long[] table;
int size;
public void increment(@NonNull E e) {
if (isNotInitialized()) {
return;
}
// 根据key的hashCode通过一个哈希函数得到一个hash值
int hash = spread(e.hashCode());
// 把一个long的64bit划分成16个等分,每一等分4个bit
// 用来定位到是哪一个等分的,用hash值低两位作为随机数,再左移2位,得到一个小于16的值
// start 值:0、4、8、12
// 3 二进制 0011,& 必定能获得小于4的数字
int start = (hash & 3) << 2;
// Loop unrolling improves throughput by 5m ops/s
// indexOf方法的意思就是,根据hash值和不同种子得到table的下标index
// 这里通过四个不同的种子,得到四个不同的下标index
int index0 = indexOf(hash, 0);
int index1 = indexOf(hash, 1);
int index2 = indexOf(hash, 2);
int index3 = indexOf(hash, 3);
// 根据index和start(+1, +2, +3)的值,把table[index]对应的等分追加1
boolean added = incrementAt(index0, start);
added |= incrementAt(index1, start + 1);
added |= incrementAt(index2, start + 2);
added |= incrementAt(index3, start + 3);
if (added && (++size == sampleSize)) {
reset();
}
}
// 作用是根据key的hashCode通过一个哈希函数得到一个hash值
// 解决 hashCode 不够均匀分散,再打散一下
// x 值为 key 的 hashcode
int spread(int x) {
x = ((x >>> 16) ^ x) * 0x45d9f3b;
x = ((x >>> 16) ^ x) * 0x45d9f3b;
return (x >>> 16) ^ x;
}
猜你喜欢
- 2024-10-20 操作系统概论:第四章 内存管理 操作系统内存管理笔记
- 2024-10-20 推荐一款nginx+redis+ehcache高并发与高可用缓存架构
- 2024-10-20 真正的缓存之王,Google Guava 只是弟弟
- 2024-10-20 操作系统-存储管理与文件管理-笔记
- 2024-10-20 图解Linux进程优先级 linux 进程优先级 线程优先级
- 2024-10-20 一文读懂进程调度算法 进程调度常用算法及其思想
- 2024-10-20 缓存最关心指标有哪些,这篇文章告诉你?
- 2024-10-20 缓存算法:LRU、LFU、随机替换等常见算法简介
- 2024-10-20 Caffine Cache 在 SpringBoot 中的使用
- 2024-10-20 力扣算法题-如何使用两个栈来实现一个FIFO的队列?
你 发表评论:
欢迎- 最近发表
-
- 在 Spring Boot 项目中使用 activiti
- 开箱即用-activiti流程引擎(active 流程引擎)
- 在springBoot项目中整合使用activiti
- activiti中的网关是干什么的?(activiti包含网关)
- SpringBoot集成工作流Activiti(完整源码和配套文档)
- Activiti工作流介绍及使用(activiti工作流会签)
- SpringBoot集成工作流Activiti(实际项目演示)
- activiti工作流引擎(activiti工作流引擎怎么用)
- 工作流Activiti初体验及在数据库中生成的表
- Activiti工作流浅析(activiti6.0工作流引擎深度解析)
- 标签列表
-
- oraclesql优化 (66)
- 类的加载机制 (75)
- feignclient (62)
- 一致性hash算法 (71)
- dockfile (66)
- 锁机制 (57)
- javaresponse (60)
- 查看hive版本 (59)
- phpworkerman (57)
- spark算子 (58)
- vue双向绑定的原理 (68)
- springbootget请求 (58)
- docker网络三种模式 (67)
- spring控制反转 (71)
- data:image/jpeg (69)
- base64 (69)
- java分页 (64)
- kibanadocker (60)
- qabstracttablemodel (62)
- java生成pdf文件 (69)
- deletelater (62)
- com.aspose.words (58)
- android.mk (62)
- qopengl (73)
- epoch_millis (61)
本文暂时没有评论,来添加一个吧(●'◡'●)