说明: 利用guava本地缓存和函数式编程来实现一个本地缓存。
注意: 因为之前缓存使用Redis来做,如果当所有的缓存都存储在Redis中的时候,一旦网络不稳定导致未及时相应,所有请求都可能被阻塞,导致内存和CPU被打满,从而引起重大问题!
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;
}