前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >本地缓存选型(Guava/Caffeine/Ohc)及性能对比

本地缓存选型(Guava/Caffeine/Ohc)及性能对比

原创
作者头像
用户7255712
修改2021-12-27 21:43:09
4.1K0
修改2021-12-27 21:43:09
举报
文章被收录于专栏:本地缓存本地缓存

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 Guava
    • 1.1) 基础使用
    • 2 CaffeineCache
      • 2.1 基础使用
      • 3 堆外缓存Ohc
      • 4 性能对比
      • 5 其他高级用法
        • 5.1 用法汇总
          • 5.2 加载策略
            • 5.3 回收策略
              • 5.4 外部存储:适用于写到数据库/多级缓存同步
                • 5.5 统计缓存使用情况
                • 6 其他:为什么Caffeine比Guava优秀
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档