Guava Cache

场景

Local Cache在业务场景中是不可或缺的一部分。当需要频繁的查询某些非实时变化的数据时,就可以考虑使用缓存, 主要有以下几种场景:

  1. 查询具有K-V特性;
  2. 获取的数据非实时变化;
  3. 频繁的查询请求;
  4. 内存使用量可接受;

LoadingCache

public LoadingCache<String, String> localCache() {
    return CacheBuilder.newBuilder().maximumSize(1000)
            .expireAfterWrite(30, TimeUnit.SECONDS)
            .refreshAfterWrite(20, TimeUnit.SECONDS)
            .build(new CacheLoader<String, String>() {
                @Override
                public String load(String key) throws Exception {
                    System.out.println("缓存不存在,加载ing->" + key);
                    String result = key + ":one";
                    return result;
                }
            });
}

我们通过CacheBuilder 生成了一个LoadingCache缓存对象

  1. maximumSize定义了缓存的容量大小,当缓存数量即将到达容量上限时,则会进行缓存回收,回收最近没有使用或总体上很少使用的缓存项。需要注意的是在接近这个容量上限时就会发生,所以需要选取比合适maximumSize稍大的值。
  2. expireAfterWrite这个方法定义了缓存的过期时间:expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读/写访问,则回收;expireAfterWrite(long, TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回收。
  3. load方法:当获取的缓存值不存在或已过期时,则会调用此load方法,进行缓存值的计算。
  4. refreshAfterWrite(long, TimeUnit):定时刷新(Guava cache的刷新需要依靠用户请求线程,让该线程去进行load方法的调用,所以如果一直没有用户尝试获取该缓存值,则该缓存也并不会刷新),这个方法非常重要,看到过很多没有使用定时刷新而带来服务大量阻塞的情况。我们知道LoadingCache对“缓存穿透”做了控制,即当大量线程用相同的key获取缓存值时,只会有一个线程进入load方法,而其他线程则等待,直到缓存值被生成;然而,这样阻塞在高并发时,会造成大量的阻塞,一招不慎,甚至线程池耗尽;所以请记得一定要使用refreshAfterWrite方法:请求某个key缓存时,更新线程调用load方法更新该缓存,其他请求线程返回该缓存的旧值。这样只有一个线程被阻塞,用来生成缓存值,而其他的线程不会被阻塞。

异步刷新

上述方式其实已经能够绝大满足生产条件需要了,对于数据库/某些密集计算的负担已经大大减小,但是对于某些特定的业务场景,某个线程的一次阻塞意味着一次请求失败,可能也是不可容忍的,这个时候异步刷新是一种更好的选择:

@Configuration
class LocalCacheConfig {

ListeningExecutorService reloadPool =
        MoreExecutors.listeningDecorator(new ThreadPoolExecutor(10, 10 , 20, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>(100)));

@Bean
public LoadingCache<String, String> localCache() {
    return CacheBuilder.newBuilder().maximumSize(1000)
            .expireAfterWrite(30, TimeUnit.SECONDS)
            .refreshAfterWrite(20, TimeUnit.SECONDS)
            .build(new CacheLoader<String, String>() {
                @Override
                public String load(String key) throws Exception {
                    System.out.println("缓存不存在,加载ing->" + key);
                    String result = key + ":one";
                    return result;
                }


                @Override
                public ListenableFuture<String> reload(String key,
                                                       String oldValue) throws Exception {
                    return reloadPool.submit(new Callable<String>() {

                        @Override
                        public String call() throws Exception {
                            System.out.println("缓存不存在,加载ing->reload");
                            return load(key);
                        }
                    });
                }

            });
}

该实现重写了CacheLoader的reload方法,将刷新缓存值的任务交给后台线程,所有的用户请求线程均返回旧的缓存值,不存在阻塞;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值