本地内存因其比较有限,所以在使用有界的内存策略时,需要对缓存数据进行淘汰,所以这里写了几个比较常见的缓存更新策略:
LFU - 最近最不常使用
淘汰最近访问次数最低的数据 , 关注的是访问次数
需要维护一个数据的访问次数,并且需要更新。开销比较的大。
LFU适合周期性、偶发性的批量操作;适合存在持续被访问的热门数据的模式 - 忽略时间因素
但是对于热点数据,访问频率高,不代表目前已经还存在意义。比如”知乎热榜“
LFU 关注的是访问次数,认为次数多的数据更有价值。
LFU 可以使用 红黑树排序 + 哈希表 的模式 [460. LFU 缓存](https://www.notion.so/460-LFU-d4273d498c9547afa40520bc7f0f291e?pvs=21)
LRU - 最近最少使用
淘汰最近最少使用的数据,即淘汰掉最近使用次数少的数据。(时间局部性原理)
LRU相比于 LFU 更加侧重于 时间上,比如一个数据的访问次数非常的高,但是已经很久没有使用了,那么他的使用次数就是0。
所以LRU相比于 LFU 少的就是 ”单位时间“, LRU 不关心数据历史中单位时间内的访问次数,只关注最近的访问次数。 - 表现为固定容量的双向链表的大小控制访问的时间。
LRU对于热点数据可以处理的很好,但是偶发性的批量操作会降低其命中率。
LRU 关注的是访问时间,认为最近访问的数据更有价值。
LRU 可以使用双向链表 + 哈希表的模式实现高效的缓存替换策略 : [146. LRU 缓存](https://www.notion.so/146-LRU-c6735dc9774e44c6919613d90db069d2?pvs=21)
TinyLFU
TinyLFU 不维护全部数据的访问频率,只维护最近一段时间之内的,所以可以过期一些无效的频率很高的数据。
新加入的数据的访问频率高于维护列表中的数据的访问频率才会被替换。
-
Tiny LFU 计算频率的逻辑
TinyLFU 是一种为了解决传统 LFU 算法空间存储比较大的问题 LFU 算法,它可以在较大访问量的场景下近似的替代 LFU 的数据统计部分,它的原理有些类似 BloomFilter。
首先回顾一下 BloomFilter 原理:在 BloomFilter 中,使用一个大的 bit 数组用于存储所有 key,每一个 key 通过多次不同的 hash 计算来映射数组的不同 bit 位,如果 key 存在将对应的 bit 位设置为 1,这样就可以通过少量的存储空间进行大量的数据过滤。
在 TinyLFU 中,把多个 bit 位看做一个整体,用于统计一个 key 的使用频率,TinyFLU 中的 key 也是通过多次不同的 hash 计算来映射多个不同的 bit 组。在读取时,取映射的所有值中的最小的值作为 key 的使用频率。
在 Caffeine 中,维护了一个 4-bit CountMinSketch 用来记录 key 的使用频率。4-bit 也就意味着,统计的 key 最大使用频率为 15,具体的实现逻辑可以参考源代码中的 FrequencySketch 类。每个 entry 使用 8 bytes 以保证准确。
W-TinyLFU
如果对于一些数据,访问量很高,但是时间周期比较久,计算频率后不足以替换掉 main hash 里面的内容的时候,此类稀疏数据命中率就比较的低。
所以这里加了一个 window cache 来保存新的记录 , 这里的 cache 机制是基于 LRU 实现的。
- 复习缓存淘汰策略(@2023-12-18)