Guava Cache最佳实践

项目中经常使用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值

参考

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • CPU Cache 今天的CPU比25年前更复杂。那时候,CPU内核的频率与内存总线的频率相当。内存访问只比寄存器...
    blueshadow阅读 3,066评论 0 5
  • 使用场景 缓存在很多场景下都是相当有用的。例如,计算或检索一个值的代价很高,并且对同样的输入需要不止一次获取值的时...
    jiangmo阅读 822评论 0 3
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,150评论 1 32
  • 关于Mongodb的全面总结 MongoDB的内部构造《MongoDB The Definitive Guide》...
    中v中阅读 32,048评论 2 89
  • 救护车呼啸而来,救护车警报声在医院急诊科已经成为习以为常的音乐。但是,眼前这个全身血肉模糊的男性患者,场面残忍程度...
    杏林煮酒阅读 159评论 0 1