概述:
Caffeine是一个基于Java8和Guava Cache重写的高性能的JVM缓存工具。
官方文档地址:https://github.com/ben-manes/caffeine
效率:
Caffeine使用了W-TinyLFU淘汰算法,使缓存命中率提升至接近最佳,同时占用的内存尽可能少。
不同类型的实现:
- Cache - 手动填充缓存:
- 需要显式的去控制缓存的创建,更新和删除。
- LoadingCache - 同步加载缓存:
- 使用CacheLoader来构建的缓存的值。批量查找可以使用
Caffeine.getAll(Iterable<? extends String>)
方法。默认情况下,getAll将会对缓存中没有值的key分别调用CacheLoader.load
方法来构建缓存的值。我们可以重写CacheLoader.loadAll
方法来提高getAll的效率。 - 如果get一个不存在的key,会调用定义好的load方法,加载一个默认值。可有效应对恶意调用不存在的key的攻击行为,防止缓存击穿。
- 使用CacheLoader来构建的缓存的值。批量查找可以使用
- AsyncLoadingCache - 异步加载缓存:
- AsyncLoadingCache是继承自LoadingCache类的,异步加载使用Executor去调用方法并返回一个CompletableFuture。异步加载缓存使用了响应式编程模型。
过期策略:
size - 基于容量/大小
Caffeine.maximumSize(long)
,例 long = 1000 表示最有同时存储1000个Entry,也就是缓存1000个K-V值。`Caffeine.estimatedSize()
返回当前已经占用的Entry数。基于权重(比较难理解,我也还没理解)Caffeine.maximumWeight(long)
。CacheBuilder.weigher(Weigher),可以指定权重函数。通过权重函数计算出当前对‘总重’。如果‘总重’超过限制,就淘汰缓存。
time - 基于时间
Caffeine.expireAfterAccess(long, TimeUnit)
缓存项在给定时间内没有被‘读/写’访问,则回收。Caffeine.expireAfterWrite(long, TimeUnit)
缓存项在给定时间内没有被写访问(创建或覆盖),则回收。Caffeine.expireAfter(Expiry)
可以在Expiry中分别自定义 读/写/创建等操作的超时时间,比上面两种策略更加灵活。
reference - 基于引用
Caffeine.weakKeys()
使用弱引用存储键。当键没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(==),使用弱引用键的缓存用==而不是equals比较键。Caffeine.weakValues()
使用弱引用存储值。当值没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(==),使用弱引用值的缓存用==而不是equals比较值。Caffeine.softValues()
使用软引用存储值。软引用只有在响应内存需要时,才按照全局最近最少使用的顺序回收。考虑到使用软引用的性能影响,建议使用更有性能预测性的缓存大小限定。使用软引用值的缓存同样用==而不是equals比较值。
Java4种引用的级别由高到低依次为:强引用 > 软引用 > 弱引用 > 虚引用
引用类型 | 被垃圾回收时间 | 用途 | 生存时间 |
---|---|---|---|
强引用 | 从来不会 | 对象的一般状态 | JVM停止运行时终止 |
软引用 | 在内存不足时 | 对象缓存 | 内存不足时终止 |
弱引用 | 在垃圾回收时 | 对象缓存 | gc运行后终止 |
虚引用 | - | - | - |
移除
- 一些术语
- eviction - 驱逐,因为过期策略而删除
- invalidation - 失效,由用户手动触发而删除
- removal - 移除结果,发生eviction或者invalidation产生的通知结果
- 任何时候,你都可以显式地清除缓存项:
- 单个清除:
Cache.invalidate(key)
- 批量清除:
Cache.invalidateAll(keys)
- 清除所有缓存项:
Cache.invalidateAll()
- 单个清除:
- 监听器
- 通过
CacheBuilder.removalListener(RemovalListener)
,你可以声明一个监听器,以便缓存项被移除时做一些额外操作。缓存项被移除时,RemovalListener会获取移除通知(RemovalNotification),其中包含移除原因(RemovalCause)、键和值。 - 监听器的操作是异步的,由executor执行。executor默认实现是
ForkJoinPool.commonPool()
。也可以通过Caffeine.executor(Executor)
设置想要的executor。
- 通过
数据统计
- 通过
Caffeine.recordStats()
可以开启数据统计。 - 调用
Cache.stats()
可以拿到CacheStats数据对象,包含: - hitRate() - 命中率
- evictionCount() - 移除的缓存数量
- averageLoadPenalty() - 加载新缓存的平均时间
- ……等
- 统计的数据也可以通过被动推送的方式拿到,这里不过多描述,有兴趣可以自己研究。
其他特性
- Tiker
- 自定义时钟,可以不使用系统默认的时钟,通过自定义的时钟控制缓存的存活时间。
- refresh
- LoadingCache的特性,刷新缓存,刷新与删除再写入的不同点在于:刷新为非阻塞操作,在并发的情况下,其他线程可以在刷新的过程中取到旧值。而eviction则会阻塞线程保证数据一致性。
- AsMap
- Caffeine可以转成ConcurrentMap进行操作。转换后的map和cache共享数据,所以更新操作会相互影响。
- 在Spring框架中使用Caffeine
- 通过Spring的CaffeineCacheManager来管理Caffeine的生命周期。
- 详情见Spring官方文档:https://docs.spring.io/spring/docs/current/spring-framework-reference/integration.html#cache-store-configuration-caffeine
DEMO
1 | /** |
1 | Logger logger = LoggerFactory.getLogger("ClassName"); |