Guava Cache最佳实践

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

示例代码

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

LoadingCache modelCache = CacheBuilder.newBuilder()
        //限制缓存大小,防止OOM
        .maximumSize(1000)
        //提供过期策略
        .expireAfterAccess(100, TimeUnit.MINUTES)
        //缓存不存在的时候,自动加载
        .build(new CacheLoader() {
            @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() {
    @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) 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 LoadingCache不能缓存null值
  • How to avoid caching when values are null?
  • 走近Guava(五): 缓存
  • Guava Cache -- Java 应用缓存神器

你可能感兴趣的:(Guava Cache最佳实践)