Guava Cache

Local Cache在业务场景中不可或缺,适用于查询有K-V特性、数据非实时变化、查询请求频繁且内存使用量可接受的场景。介绍了通过CacheBuilder生成LoadingCache缓存对象,包括容量、过期时间等设置,还提到定时刷新和异步刷新策略,以减少阻塞。

场景

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方法,将刷新缓存值的任务交给后台线程,所有的用户请求线程均返回旧的缓存值,不存在阻塞;

### Guava 缓存的使用与实现 #### 创建和配置缓存实例 `LocalCache` 是整个 Guava Cache 的核心类,包含了数据结构以及基本操作方法[^1]。通过 `CacheBuilder` 类可以方便地构建并定制化缓存对象。 ```java import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; // 构建一个简单的本地缓存实例 Cache<String, String> cache = CacheBuilder.newBuilder() .maximumSize(100) // 设置最大条目数为100 .build(); ``` #### 自定义加载器 对于更复杂的场景,可以通过指定 `CacheLoader` 来自动填充缺失键对应的值: ```java import com.google.common.base.Optional; import com.google.common.cache.CacheLoader; Cache<String, Optional<String>> loadingCache = CacheBuilder.newBuilder() .maximumSize(100) .build(new CacheLoader<String, Optional<String>>() { @Override public Optional<String> load(String key) throws Exception { return Optional.ofNullable(fetchValueForKey(key)); } }); ``` 这里展示了如何设置当请求不存在于缓存中的时候,默认调用给定的方法来获取该值,并将其加入到缓存中以便后续访问[^2]。 #### 添加、检索及移除元素 可以直接利用 put 方法向缓存添加新项;getIfPresent 可用于查询特定键是否存在及其关联值;而 invalidate 则允许删除单个或全部记录。 ```java // 向缓存中添加一项 cache.put("key", "value"); // 获取已存在的项 String value = cache.getIfPresent("key"); // 移除某项 cache.invalidate("key"); ``` #### 高级特性支持 Guava 还提供了诸如过期时间(expireAfterWrite/expireAfterAccess)、刷新机制(refresh)等功能的支持,这些都极大地方便了开发者针对不同应用场景灵活调整缓存行为。 #### 并发控制优化 由于设计之初就考虑到了多线程环境下的高效运作,因此即使是在高并发读写的条件下也能保持良好的性能表现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值