Caffeine
一个高性能的缓存库,Caffeine 使用 Window TinyLfu 回收策略,可以提供了一个近乎最佳的命中率。
依赖
maven
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.5.5</version>
</dependency>
Grade
compile 'com.github.ben-manes.caffeine:caffeine:2.5.5'
基本用法
@Test
public void baseTest() {
Cache<String, Data> cache = Caffeine.newBuilder()
// 设置过期时间
.expireAfterWrite(1, TimeUnit.MINUTES)
// 缓存数量
.maximumSize(100).build();
// 往缓存中设置
cache.put("A", new Data("A"));
Assert.assertEquals(cache.getIfPresent("A").getData(), "A");
// 取值 若存在返回 不存在返回null
Data b = cache.getIfPresent("B");
Assert.assertNull(b);
// 取值 若不存在 则按后面计算并返回
b = cache.get("B", k -> new Data(k));
Assert.assertEquals(cache.getIfPresent("B").getData(), "B");
// 手动移除key
cache.invalidate("A");
Assert.assertNull(cache.getIfPresent("A"));
}
同步加载
@Test
public void syncLoadTest() {
LoadingCache<String, Data> cache = Caffeine.newBuilder()
.maximumSize(100)
.expireAfterWrite(1, TimeUnit.MINUTES)
.build(k -> new Data(k));
//这个获取的仍然为空
Assert.assertNull(cache.getIfPresent("A"));
// get() 检索值 此处会根据初始化时指定的方法初始化value
Data a = cache.get("a");
Assert.assertNotNull(a);
Assert.assertEquals(a.getData(),"a");
}
异步加载
@Test
public void asyncLoadTest() {
AsyncLoadingCache<String, Data> cache = Caffeine.newBuilder().maximumSize(100).expireAfterWrite(1, TimeUnit.MINUTES).buildAsync(k -> new Data(k));
cache.get("A").thenAccept(data -> {
Assert.assertNotNull(data);
Assert.assertEquals(data.getData(), "a");
});
}
这3种加载方式使用的Cache类不同,可以根据自己的需求来选择合适的方式来加载数据
回收策略
-
基于maximumSize()设置的大小,若果超过最大值后将异步回收
-
基于权重大小删除元素
- .maximumWeight():指定缓存的最大容量,当缓存超出这个容量的时候,会使用Window TinyLfu策略来删除缓存
- .weighter((k,v)->{}):计算权重方法
- maximumWeight与maximumSize不可以同时使用。
-
基于时间回收
- 访问后到期 — 从上次读或写发生后,条目即过期
- 写入后到期 — 从上次写入发生之后,条目即过期
- 自定义策略 — 到期时间由 Expiry 实现独自计算
-
基于引用
我们可以将缓存的驱逐配置成基于垃圾回收器。为此,我们可以将key 和 value 配置为弱引用或只将值配置成软引用。
- Caffeine.weakKeys():使用弱引用存储key。如果没有其他地方对该key有强引用,那么该缓存就会被垃圾回收器回收。
- Caffeine.weakValues():使用弱引用存储value。
- Caffeine.softValues():使用软引用存储value。当内存满了过后,软引用的对象以将使用最近最少使用(least-recently-used ) 的方式进行垃圾回收。
Ehcache
Ehcache 从 Hibernate 发展而来,逐渐涵盖了 Cahce 界的全部功能,是目前发展势头最好的一个项目。具有快速,简单,低消耗,依赖性小,扩展性强,支持对象或序列化缓存,支持缓存或元素的失效,提供 LRU、LFU 和 FIFO 缓存策略,支持内存缓存和磁盘缓存,分布式缓存机制等等特点。
功能
- 支持内存及磁盘存储
- 多种缓存策略
- 支持多缓存管理器实例,以及一个实例的多个缓存区域
缓存淘汰策略
- FIFO:先进先出
- LFU:最少被使用,缓存的元素有一个hit属性,hit值最小的将会被清出缓存
- LRU:最近最少使用,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存
依赖
maven
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.7.0</version>
</dependency>
gradle
compile 'org.ehcache:ehchache:3.7.0'
基本用法
public class EhCache implements LocalCache<String, Data> {
private CacheManager cacheManager;
private Cache<String, Data> cache;
public EhCache() {
// 创建缓存管理器 一个缓存管理器可以关联多个缓存
cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build();
// 初始化
cacheManager.init();
// 创建 dataCache缓存
CacheConfigurationBuilder builder = CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, Data.class, ResourcePoolsBuilder.heap(100));
cache = cacheManager.createCache("dataCache", builder);
}
@Override
public Data get(String K) {
return cache.get(K);
}
@Override
public void set(String K, Data data) {
cache.put(K, data);
}
}
2种缓存框架对比
特性
从支持的功能上来看,ehcache支持的功能特性更多一些,比如多级缓存,分布式缓存,缓存监听器等等。但如果只是想简单的使用一个内存缓存组件,并且对性能有较高的要求时,可以优先考虑Caffeine,它的性能比ehcache更好,并且它使用了更加优秀的缓存淘汰策略,提供接近理想的命中率,在Spring5中已经作为基础的缓存组件使用了。
易用性
从我简单使用上来看,Caffeine提供的api更友好一些,而且ehcache似乎没有提供异步加载特性。配置也略微烦琐了些。
性能
1 读多写少场景
测试条件
1.缓存1000个数据
2.8个线程读 2个线程写
Caffeine(读/写) | Ehcache(读/写) | |
---|---|---|
1 | 1558360022/184773156 | 1794188434/155912462 |
2 | 1861119523/288443676 | 1818122432/147250831 |
3 | 1421957913/277856668 | 1348268819/142143289 |
平均 | 1613812486/250357833 | 1653526561/148435527 |
2 只读场景
测试条件
1.缓存1000个数据
2.10个线程读
Caffeine(读) | Ehcache(读) | |
---|---|---|
1 | 1723628225 | 2129962082 |
2 | 1899030885 | 2025097805 |
3 | 2124612664 | 2086497546 |
平均 | 1915757258 | 2080519144 |
只读场景下Caffeine性能比Ehcache略微差一些。
总结
在读性能上两者差距不大,但在写性能上Caffeine明显比Ehcache要高出不少。综合易用性,性能来看,我认为一般项目使用Caffeine就足够了。