1 Guava
1.1) 基础使用
代码语言:javascript复制@Slf4j
public class GuavaCache {
private static Cache<String, Object> cache = CacheBuilder.newBuilder()
.maximumSize(1000000)
.expireAfterWrite(60, TimeUnit.SECONDS)
.concurrencyLevel(4)
.initialCapacity(1000)
//配置上recordStats,cache.stats()才能生效
//.recordStats()
.removalListener(new RemovalListener<String, Object>() {
@Override
public void onRemoval(RemovalNotification<String, Object> rn) {
}
}).build();
/*
*
* @desction: 获取缓存
*/
public static Object get(String key) {
try {
return StringUtils.isNotEmpty(key) ? cache.getIfPresent(key) : null;
} catch (Exception e) {
log.error("local cache by featureId 异常", e);
return null;
}
}
/*
*
* @desction: 放入缓存
*/
public static void put(String key, Object value) {
if (StringUtils.isNotEmpty(key) && value != null) {
cache.put(key, value);
}
}
/*
*
* @desction: 移除缓存
*/
public static void remove(String key) {
if (StringUtils.isNotEmpty(key)) {
cache.invalidate(key);
}
}
/*
*
* @desction: 批量删除缓存
*/
public static void remove(List<String> keys) {
if (keys != null && keys.size() > 0) {
cache.invalidateAll(keys);
}
}
public static CacheStats getStats() {
return cache.stats();
}
/**
* test
*
* @param args
*/
public static void main(String[] args) {
long start = System.currentTimeMillis();
//只测试写入
for (int j = 0; j < 500000; j ) {
GuavaCache.put("" j, j);
}
//测试读写
for (int j = 0; j < 500000; j ) {
GuavaCache.get("" j);
}
//读写 读为命中
for (int j = 0; j < 500000; j ) {
GuavaCache.get("" j "noHits");
}
GuavaCache.cache.cleanUp();
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
2 CaffeineCache
2.1 基础使用
代码语言:javascript复制@Slf4j
public class CaffeineCache {
private static Cache<String, Object> caffeineCache = Caffeine.newBuilder()
.maximumSize(1000000)
.expireAfterWrite(60, TimeUnit.SECONDS)
.initialCapacity(1000)
//配置上recordStats,cache.stats()才能生效
//.recordStats()
.removalListener(new RemovalListener<String, Object>() {
@Override
public void onRemoval(@Nullable String key, @Nullable Object value, @NonNull RemovalCause cause) {
}
})
.build();
/*
*
* @desction: 获取缓存
*/
public static Object get(String key) {
try {
return StringUtils.isNotEmpty(key) ? caffeineCache.getIfPresent(key) : null;
} catch (Exception e) {
log.error("local cache by featureId 异常", e);
return null;
}
}
/*
*
* @desction: 放入缓存
*/
public static void put(String key, Object value) {
if (StringUtils.isNotEmpty(key) && value != null) {
caffeineCache.put(key, value);
}
}
/*
*
* @desction: 移除缓存
*/
public static void remove(String key) {
if (StringUtils.isNotEmpty(key)) {
caffeineCache.invalidate(key);
}
}
/*
*
* @desction: 批量删除缓存
*/
public static void remove(List<String> keys) {
if (keys != null && keys.size() > 0) {
caffeineCache.invalidateAll(keys);
}
}
public static CacheStats getStats() {
return caffeineCache.stats();
}
/**
* test
*
* @param args
*/
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
//只测试写入
for (int j = 0; j < 500000; j ) {
CaffeineCache.put("" j, j);
}
//测试读写
for (int j = 0; j < 500000; j ) {
CaffeineCache.get("" j);
}
//读写 读为命中
for (int j = 0; j < 500000; j ) {
CaffeineCache.get("" j "noHits");
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
3 堆外缓存Ohc
代码语言:java复制@Slf4j
public class OhcCache {
private static OHCache<String, String> ohCache = OHCacheBuilder.<String, String>newBuilder()
.keySerializer(new OhcStringSerializer())
.valueSerializer(new OhcStringSerializer())
//.hashMode(HashAlgorithm.CRC32C)
//单位是字节,默认2GB空间
.capacity(2 * 1024 * 1024 * 1024L)
.timeouts(true)
.defaultTTLmillis(600 * 1000)
.eviction(Eviction.LRU)
.build();
/**
* 设置值
*
* @param k
* @param v
* @return
*/
public static boolean put(String k, String v) {
return put(k, v, 9223372036854775807L);
}
public static boolean put(String k, String v, Long time) {
try {
return ohCache.put(k, v, time);
} catch (Exception e) {
log.error("ohc cache put error", e);
return false;
}
}
public static String get(String k) {
return ohCache.get(k);
}
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
//只测试写入
for (int j = 0; j < 500000; j ) {
OhcCache.put("" j, j "");
}
System.out.println("写入耗时:" (System.currentTimeMillis() - start));
//测试读写
for (int j = 0; j < 500000; j ) {
OhcCache.get("" j);
}
System.out.println("读取命中耗时:" (System.currentTimeMillis() - start));
//读写 读为命中
for (int j = 0; j < 500000; j ) {
OhcCache.get("" j "noHits");
}
System.out.println("读取未命中耗时:" (System.currentTimeMillis() - start));
System.out.println("总耗时:" (System.currentTimeMillis() - start));
}
}
4 性能对比
对比数据
类型 | 50万写入:耗时:ms | 50写 50读(读全命中):耗时:ms | 50万写 50万读(全命中) 50万读(未命中):耗时:ms | 50万读 50万未命中 |
---|---|---|---|---|
Guava | 329/340/326/328/328 | 536/518/546/525/558 | 647/646/638/668/641 | 490/501/482/485/492 |
Caffeine | 292/284/270/279/267 | 414/382/353/385/361 | 479/513/460/487/481 | 343/326/333/336/369 |
Ohc | 448/433/430/446/442 | 763/748/765/741/705 | 918/947/901/964/903 | 653/676/607/639/704 |
Ohc-Obj | 1343/1315/1217/1249/1193 | 1910/1830/1849/1803/1786 | 1979/1965/1947/1968/1946 | 1487/1573/1499/1491/1483 |
总结
代码语言:javascript复制无论是读写,Caffeine性能都比Guava要好。
Caffeine基于java8的高性能,接近最优的缓存库。
Caffeine提供的内存缓存使用参考Google guava的API。
Caffeine是基于Google guava和 ConcurrentLinkedHashMap的设计经验上改进的成果。
Caffeine是Spring 5默认支持的Cache,可见Spring对它的看重,Spring抛弃Guava转向了Caffeine。
5 其他高级用法
5.1 用法汇总
代码语言:javascript复制通过异步自动加载实体到缓存中
基于大小的回收策略
基于时间的回收策略
自动刷新
key自动封装虚引用
value自动封装弱引用或软引用
实体过期或被删除的通知
写入外部资源
统计累计访问缓存
5.2 加载策略
手动加载
代码语言:javascript复制// 检索一个entry,如果没有则为null
Graph graph = cache.getIfPresent(key);
// 检索一个entry,如果entry为null,则通过key创建一个entry并加入缓存
graph = cache.get(key, k -> createExpensiveGraph(key));
// 插入或更新一个实体
cache.put(key, graph);
// 移除一个实体
cache.invalidate(key);
同步加载
构造Cache时候,build方法传入一个CacheLoader实现类。实现load方法,通过key加载value。
代码语言:javascript复制.build(key -> createExpensiveGraph(key));
异步加载
代码语言:javascript复制 .buildAsync((key, executor) -> createExpensiveGraphAsync(key, executor));
5.3 回收策略
Caffeine提供了3种回收策略:基于大小回收,基于时间回收,基于引用回收
5.4 外部存储:适用于写到数据库/多级缓存同步
代码语言:javascript复制//通过CacheWriter 可以将缓存回写的外部存储中。
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.writer(new CacheWriter<Key, Graph>() {
@Override public void write(Key key, Graph graph) {
// 写入到外部存储或二级缓存
}
@Override public void delete(Key key, Graph graph, RemovalCause cause) {
// 删除外部存储或者二级缓存
}
})
.build(key -> createExpensiveGraph(key));
5.5 统计缓存使用情况
代码语言:java复制通过使用Caffeine.recordStats(), 可以转化成一个统计的集合. 通过 Cache.stats() 返回一个CacheStats。
CacheStats提供以下统计方法
ps://配置上recordStats----cache.stats()才能生效
hitRate(): 返回缓存命中率
evictionCount(): 缓存回收数量
averageLoadPenalty(): 加载新值的平均时间
6 其他:为什么Caffeine比Guava优秀
其他压测参考:https://github.com/ben-manes/caffeine/wiki/Benchmarks