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

2021-12-27 21:43:09 浏览数 (1)

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

0 人点赞