guava实现本地缓存工具

158 阅读2分钟

说明: 利用guava本地缓存和函数式编程来实现一个本地缓存。

注意: 因为之前缓存使用Redis来做,如果当所有的缓存都存储在Redis中的时候,一旦网络不稳定导致未及时相应,所有请求都可能被阻塞,导致内存和CPU被打满,从而引起重大问题!

image.png

1. 引入相关依赖

<!-- guava相关依赖 -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>19.0</version>
</dependency>
<!-- fastjson相关依赖 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.75</version>
</dependency>

2. 定义CaCheUtil类

说明: 此处只有查询时添加缓存的操作,没有实现编辑时删除缓存的操作,可自行实现!

当批量查询商品信息时List(Long id)返回值为Map<id, sku_info>,因为不了解info类型,所以可以使用泛型代替(函数式编程),或者使用反射(复杂)

@Component
@Slf4j
public class CacheUtil<K, V> {

    //先初始化Cache<String, String> 要操作的缓存数据key和value都是String类型
    private Cache<String, String> localCache = CacheBuilder.newBuilder()
            .maximumSize(5000) //缓存的最大元素数量
            .expireAfterWrite(3, TimeUnit.SECONDS) //过期时间
            .build();

    @Value("${guava.cache.switch}")
    public Boolean switchCache; //后期 配合nacos注册中心动态获取值来判断是否开启本地缓存

    /**
     * 二级缓存先从redis中获取数据
     * @param skuIdList id集合
     * @param cachePrefix 缓存前缀
     * @param clazz 对结果进行序列化,泛型为V
     * @param function Function<入参, 出参>
     * @return
     */
    public Map<K, V> getResult(List<K> skuIdList, String cachePrefix,
                               Class<V> clazz, Function<List<K>, Map<K, V>> function) {
        //参数校验
        if(CollectionUtils.isEmpty(skuIdList)) {
            return Collections.emptyMap();
        }

        Map<K, V> resultMap = new HashMap<>(16);

        if(!switchCache) { //不走本地缓存,直接调用方法
            resultMap = function.apply(skuIdList); //apply调用传入的方法,获取返回值
            return resultMap;
        }

        //id集合可能有些id对应的数据本地缓存没有,把该id存在这个集合中
        List<K> noCacheListId = new ArrayList<>();

        //本地缓存中有的存map,没有的将id存到noCacheListId集合中
        for(K id : skuIdList) {
            String cacheKey = cachePrefix + "_" + id;
            String content = localCache.getIfPresent(cacheKey); //获取本地缓存中key对应的value
            if(StringUtils.isNotBlank(content)) {
                V v = JSON.parseObject(content, clazz); //不为空,把查询结果转为json字符串
                resultMap.put(id, v);
            } else {
                noCacheListId.add(id);
            }
        }

        if(CollectionUtils.isEmpty(noCacheListId)) { //本地缓存中全部存在id集合对应的数据
            return resultMap;
        }

        //调用方法获取map
        Map<K, V> noCacheResultMap = function.apply(noCacheListId);

        if(CollectionUtils.isEmpty(noCacheResultMap)) {
            return resultMap;
        }

        //把调用方法的结果存到map和本地缓存中
        for(Map.Entry<K, V> map : noCacheResultMap.entrySet()) {
            K id = map.getKey();
            V value = map.getValue();
            resultMap.put(id, value);
            String cacheKey = cachePrefix + "_" + id;
            localCache.put(cacheKey, JSON.toJSONString(value));
        }
        return resultMap;
    }
}

3. 外部调用

@GetMapping("testLocalCache")
public void testLocalCache(@RequestParam List<Long> ids) {

    //获取SkuInfo商品信息

    //()里的为入参,声明入参为list,eg:id-> userService.queryById(id) 我们的方法未使用入参参数list
    cacheUtil.getResult(ids, "skuInfo.skuName", SkuInfo.class, (list) -> {
        Map<Long, SkuInfo> skuInfo = getSkuInfo(ids); //获取数据
        return skuInfo;
    });
}

//定义两个方法,模拟获取商品信息和商品价格
public Map<Long, SkuInfo> getSkuInfo(List<Long> ids) {
    return Collections.emptyMap();
}
public Map<Long, SkuPrice> getSkuPrice(List<Long> ids) {
    return Collections.emptyMap();
}

//定义两个内部类,商品信息和商品价格
class SkuInfo {
    private Long id;
    private String name;
}

class SkuPrice {
    private Long id;
    private Double price;
}