Guava Cache

摘要: 学习Google内部使用的工具包Guava,在Java项目中轻松地增加缓存,提高程序获取数据的效率; 业务实现上需要用到本地缓存来解决一些数据量相对较小但是频繁访问的数据

Guava Cache适用场景:
你愿意消耗一部分内存来提升速度;
你已经预料某些值会被多次调用;
缓存数据不会超过内存总量;

Guava Cache是一个全内存的本地缓存实现,它提供了线程安全的实现机制。整体上来说Guava cache 是本地缓存的不二之选,简单易用,性能好

Guava Cache与ConcurrentMap很相似,但也不完全一样。最基本的区别是ConcurrentMap会一直保存所添加的元素,直到显式的移除;Guava Cache为了限制内存的占用,通常都是设定为自动回收元素。在某些场景下,尽管LoadingCahe不回收元素,但是它还是很有用的,因为它会自动加载缓存。

依赖:
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>23.0</version>
</dependency>


import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

// 获取历史版本的频道商品列表
private LoadingCache<String, List<Bean>> beanCache;

@PostConstruct
public void init() {
    beanCache = CacheBuilder.newBuilder()
                //过期时间5分钟(时间段内没有更新就会被回收)
                .expireAfterWrite(5 * 60, TimeUnit.SECONDS).
                //缓存数量为5000个
                .maximumSize(5000)
                .build(new CacheLoader<String, List<Bean>>() {
                    @Override
                    public List<Bean> load(String key) throws Exception {
                        return this.getBeanInfo(key);
                    }
                });
}

 

刷新机制:包括refresh和expire刷新机制

expireAfterAccess: 当缓存项在指定的时间段内没有被读或写就会被回收。

expireAfterWrite:当缓存项在指定的时间段内没有更新就会被回收。

refreshAfterWrite:当缓存项上一次更新操作之后的多久会被刷新。

考虑到时效性,我们可以使用expireAfterWrite,使每次更新之后的指定时间让缓存失效,然后重新加载缓存。guava cache会严格限制只有1个加载操作,这样会很好地防止缓存失效的瞬间大量请求穿透到后端引起雪崩效应。
     然而,通过分析源码,guava cache在限制只有1个加载操作时进行加锁,其他请求必须阻塞等待这个加载操作完成;而且,在加载完成之后,其他请求的线程会逐一获得锁,去判断是否已被加载完成,每个线程必须轮流地走一个“”获得锁,获得值,释放锁“”的过程,这样性能会有一些损耗。这里由于我们计划本地缓存1秒,所以频繁的过期和加载,锁等待等过程会让性能有较大的损耗。

     refreshAfterWrite的特点是: 在refresh的过程中,严格限制只有1个重新加载操作,而其他查询先返回旧值,这样有效地可以减少等待和锁争用,所以refreshAfterWrite会比expireAfterWrite性能好。但是它也有一个缺点,因为到达指定时间后,它不能严格保证所有的查询都获取到新值。guava cache并没使用额外的线程去做定时清理和加载的功能,而是依赖于查询请求。在查询的时候去比对上次更新的时间,如超过指定时间则进行加载或刷新。所以,如果使用refreshAfterWrite,在吞吐量很低的情况下,如很长一段时间内没有查询之后,发生的查询有可能会得到一个旧值(这个旧值可能来自于很长时间之前),这将会引发问题。

 

先执行postProcessBeforeInitialization,然后是afterPropertiesSet,再然后是init-method,最后是postProcessAfterInitialization。

 

/**
 * Created with IntelliJ IDEA.
 *
 * @Description:
 * @author: bowang
 * @create: 2019-06-04 下午3:30
 **/
public class AsyncCacheLoader {
    private static final Logger LOGGER = LoggerFactory.getLogger(AsyncCacheLoader.class);

    public static <K, V> CacheLoader<K, V> buildAysncCacheLoader(final Function<K, V> function,
                                                                 final ThreadPoolExecutor executor) {
        return CacheLoader.asyncReloading(new CacheLoader<K, V>() {
            @Override
            public V load(K k) throws Exception {
                LOGGER.info("线程{}刷新缓存", Thread.currentThread().getId());
                try {
                    return function.apply(k);
                } finally {
                    LOGGER.info("线程{}刷新缓存结束", Thread.currentThread().getId());
                }
            }
        }, executor);
    }
}


private LoadingCache<BeaverLinkSkuListRequest, List> shippingRegionInfoCache = CacheBuilder.newBuilder()
                /** 基于容量的回收 1000 个key */
                .maximumSize(10)
                /** 5s */
                .refreshAfterWrite(5,TimeUnit.SECONDS)
                /** 初始容量 */
                .initialCapacity(20)
                .build(AsyncCacheLoader.buildAysncCacheLoader(this::refreshInfo, ThreadPoolUtils.executorService));


//注意key  一般都是String, Integer, 其他bean看情况重写hashcode和equals
private List refreshInfo(BeaverLinkSkuListRequest request) {
    List<String> list = new ArrayList<>();
    if (null != request && StringUtils.isBlank(request.getName())) {
        return getBeaverLinkSkuList(request.getCommonParams(), request.getBeaverLink(), request.getLimitSize());
    } else {
        return getSuperBeaverLinkSkuList(request.getCommonParams(), request.getBeaverLink(), request.getLimitSize());
    }
}

 

相关学习文档:http://ifeve.com/google-guava-cachesexplained/

### 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、付费专栏及课程。

余额充值