美文网首页Java
Guava Cache最佳实践

Guava Cache最佳实践

作者: 十毛tenmao | 来源:发表于2019-01-04 22:09 被阅读154次

    项目中经常使用Guava Cache,根据经验总结了一些最佳实践。

    示例代码

    快速有效的使用示例如下:

    LoadingCache<Integer, Model> modelCache = CacheBuilder.newBuilder()
            //限制缓存大小,防止OOM
            .maximumSize(1000)
            //提供过期策略
            .expireAfterAccess(100, TimeUnit.MINUTES)
            //缓存不存在的时候,自动加载
            .build(new CacheLoader<Integer, Model>() {
                @Override
                public Model load(@Nonnull Integer key) {
                    Model model = doGetModel(key);
                    if (model == null) {
                        //guava不支持null,会抛出异常InvalidCacheLoadException,最佳办法是抛出自定义异常
                        throw new ModelNotFoundException();
                    }
                    return model;
                }
            });
    

    最佳实践

    自动加载

    如果缓存不存在,则自动去数据源加载数据到缓存

    .build(new CacheLoader<Integer, Model>() {
        @Override
        public Model load(@Nonnull Integer key) {
            return doGetModel(key);
        }
    )
    

    内存控制

    使用缓存一定要防止缓存占用过多的内存,导致程序OOM。需要对缓存的内存使用量进行限制,同时还需要设置过期或刷新策略。

    //限制缓存大小,防止OOM
    .maximumSize(1000)
    //提供过期策略
    .expireAfterAccess(100, TimeUnit.MINUTES)
    

    上面是使用得最多的两个选项,其他选项还有:

    • maximumWeight:限制最大权重(权重的计算方式需要传递Weigher
    • expireAfterWrite:写入后多长时间过期
    • refreshAfterWrite:写入后多长时间刷新

    removal listener

    在一些场景下,监控cache的换出结果,方便做出响应,比如在集群本地缓存同步的时候,可以监听后同步给集群内其他机器。参见:本地缓存同步的一个简单方案

    .removalListener((RemovalListener<Integer, Model>) notification -> {
        log.info("remove taskbot from guava cache: key[{}], cause[{}]", notification.getKey(), notification.getCause());
        final RemovalCause cause = notification.getCause();
        switch (cause) {
            case EXPIRED:
                log.info("model evicted because of expiration in guava cache: {}", notification.getKey());
                break;
            case SIZE:
                log.info("model evicted because of size in guava cache: {}", notification.getKey());
                break;
            case COLLECTED:
                //如果是缓存到期等原因被删除,则需要通知分布式环境下的其他机器也要删除
                log.info("model evicted because of gc in guava cache: {}", notification.getKey());
                break;
            case EXPLICIT:
                log.info("model evicted because of explicit in guava cache: {}", notification.getKey());
                break;
            case REPLACED:
                log.info("model updated because of replaced in guava cache: {}", notification.getKey());
                break;
            default:
                log.error("there should not be [{}]", cause);
        }
    })
    
    

    查看缓存统计值

    可以了解缓存使用的特性,比如命中率等

    CacheStats Cache#stats();
    
    public final class CacheStats {
      //命中次数
      private final long hitCount;
      //击穿次数
      private final long missCount;
      //加载成功次数
      private final long loadSuccessCount;
      //加载发生异常的次数
      private final long loadExceptionCount;
      //加载时间总机
      private final long totalLoadTime;
      //换出的次数
      private final long evictionCount;
    }
    

    不常用功能

    weakKey, weakValue, softValue

    使用这些值可以把内存的使用量交给JVM来控制,一般不太实用

    NULL值的处理

    GauvaCache不支持null值的缓存,而且会抛出异常InvalidCacheLoadException,最佳办法是抛出自定义异常,然后在Cache#get的时候捕捉定义异常。示例如下:

    @Override
    public Model load(@Nonnull Integer key) {
        Model model = doGetModel(key);
        if (model == null) {
            //guava不支持null,会抛出异常InvalidCacheLoadException,最佳办法是抛出自定义异常
            throw new ModelNotFoundException();
        }
        return model;
    }
    
    try {
        modelCache.get(key);
    } catch (ExecutionException e) {
        //TODO: handle exception
    } catch (ModelNotFoundException e) {
        //TODO: handle exception
    }
    

    注意事项

    • GauvaCache异步刷新缓存,不会阻塞线程获取缓存内容(老的内容)
    • GauvaCache不支持缓存null值

    参考

    相关文章

      网友评论

        本文标题:Guava Cache最佳实践

        本文链接:https://www.haomeiwen.com/subject/soflrqtx.html