大众点评项目 缓存工具封装
- 需求:缓存工具封装
- 业务实现
- 代码总览
- 总结
SpringCloud章节复习已经过去,新的章节Redis开始了,这个章节中将会回顾Redis实战项目 大众点评
主要依照以下几个原则
- 基础+实战的Demo和Coding上传到我的代码仓库
- 在原有基础上加入一些设计模式,stream+lamdba等新的糖
- 通过DeBug调试,进入组件源码去分析底层运行的规则和设计模式
代码会同步在我的gitee中去,觉得不错的同学记得一键三连求关注,感谢:
Redis优化-链接: RedisUnitToolProject
需求:缓存工具封装
我们已经在上面三节处理了各种细节:缓存穿透、缓存击穿、缓存雪崩
我们在开发过程中,如果每次缓存操作都处理一遍,那太繁琐了
为了开发的流畅和严谨,我们在这一节整合以往的四种处理方案,将其封装成工具类,这样就可以直接调工具,而不是RedisTemplate
业务实现
其实代码和上几节基本一直,但是为了封装处理, 需要回顾线程池、函数式编程接口 和泛型使用
@Slf4j
@Component
public class CacheClient {
@Resource
private StringRedisTemplate stringRedisTemplate;
//需要回顾线程池、函数式编程接口 和泛型使用
private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
定义好对应的属性
public void set(String key, Object value, Long time, TimeUnit unit){
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
}
public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit){
RedisData redisData = new RedisData();
redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
redisData.setObject(value);
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
}
定义好对应的两种存储方法,分别是通过为了 缓存穿透, 缓存击穿
缓存穿透
//通过泛型来进行处理不可知的类型,通过函数式编程传入IT用户的查询信息
public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type,
Function<ID, R> dbFallback, Long time, TimeUnit unit){
log.debug("RedisTool method...");
String key = keyPrefix + id;
String json = stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isNotBlank(json)) {
return JSONUtil.toBean(json, type);
}
if(json!=null){
return null;
}
//数据库查询怎么办,通过传入函数式编程处理
R r = dbFallback.apply(id);
if(r==null){
stringRedisTemplate.opsForValue().set(key, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
return null;
}
this.set(key, r, time, unit);
return r;
}
逻辑过期时间处理缓存击穿
// 逻辑过期时间 通过线程池进行处理
public <ID, R> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type,
Function<ID, R> dbFallback, Long time, TimeUnit unit){
log.debug("RedisTool queryWithLogicalExpire Method...");
String key = keyPrefix + id;
String json = stringRedisTemplate.opsForValue().get(key);
if(StrUtil.isBlank(json)){
return null;
}
RedisData data = JSONUtil.toBean(json, RedisData.class);
// Shop shop = JSONUtil.toBean((JSONObject) data.getObject(), Shop.class);
JSONObject object = (JSONObject) data.getObject();
R r = JSONUtil.toBean(object, type);
if(data.getExpireTime().isAfter(LocalDateTime.now())){
return r;
}
//过期了,就通过独立线程 -> 互斥锁处理缓存击穿
String lockKey = RedisConstants.LOCK_SHOP_KEY + id;
if (!tryLock(lockKey)) {
log.debug("lockKey: " + lockKey + " Result: " + r);
CACHE_REBUILD_EXECUTOR.submit(()->{
try {
this.saveShop2Redis(id, dbFallback, keyPrefix, time, unit);
} catch (Exception e) {
throw new RuntimeException(e);
/*e.printStackTrace();*/
}finally {
//删除暂时的key, 释放互斥锁
stringRedisTemplate.delete(lockKey);
}
});
}
if(r==null){
return null;
}
return r;
}
public boolean tryLock(String key){
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", RedisConstants.LOCK_SHOP_TTL, TimeUnit.SECONDS);
//这里不可以直接返回flag,因为在拆箱过程中可能出现flag为null的情况;
return BooleanUtil.isTrue(flag);
}
public <ID, R> void saveShop2Redis(ID id,
Function<ID, R> dbFallback,
String keyPrefix,
Long time,
TimeUnit unit){
R r = dbFallback.apply(id);
setWithLogicalExpire(keyPrefix + id, r, time, unit);
}
}
这样我们就可以直接去调用了,
queryWithLogicalExpire
@Override
public Result queryById(Long id) {
Shop shop = cacheClient.queryWithLogicalExpire(RedisConstants.CACHE_SHOP_KEY, id, Shop.class, this::getById,
RedisConstants.CACHE_NULL_TTL, TimeUnit.HOURS);
if(shop==null){
return Result.fail("店铺不存在");
}
return Result.ok(shop);
}
queryWithPassThrough
public Result queryById(Long id) {
/* Shop shop = cacheClient.queryWithPassThrough(key, id, Shop.class, idf -> getById(idf),
RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);*/
Shop shop = cacheClient.queryWithPassThrough(RedisConstants.CACHE_SHOP_KEY, id, Shop.class, this::getById,
RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
if(shop==null){
return Result.fail("店铺不存在");
}
return Result.ok(shop);
}
代码总览
@Slf4j
@Component
public class CacheClient {
@Resource
private StringRedisTemplate stringRedisTemplate;
//需要回顾线程池、函数式编程接口
// 和泛型使用
private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
public void set(String key, Object value, Long time, TimeUnit unit){
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
}
public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit){
RedisData redisData = new RedisData();
redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
redisData.setObject(value);
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
}
//通过泛型来进行处理不可知的类型,通过函数式编程传入IT用户的查询信息
public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type,
Function<ID, R> dbFallback, Long time, TimeUnit unit){
log.debug("RedisTool method...");
String key = keyPrefix + id;
String json = stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isNotBlank(json)) {
return JSONUtil.toBean(json, type);
}
if(json!=null){
return null;
}
//数据库查询怎么办,通过传入函数式编程处理
R r = dbFallback.apply(id);
if(r==null){
stringRedisTemplate.opsForValue().set(key, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
return null;
}
this.set(key, r, time, unit);
return r;
}
// 逻辑过期时间 通过线程池进行处理
public <ID, R> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type,
Function<ID, R> dbFallback, Long time, TimeUnit unit){
log.debug("RedisTool queryWithLogicalExpire Method...");
String key = keyPrefix + id;
String json = stringRedisTemplate.opsForValue().get(key);
if(StrUtil.isBlank(json)){
return null;
}
RedisData data = JSONUtil.toBean(json, RedisData.class);
// Shop shop = JSONUtil.toBean((JSONObject) data.getObject(), Shop.class);
JSONObject object = (JSONObject) data.getObject();
R r = JSONUtil.toBean(object, type);
if(data.getExpireTime().isAfter(LocalDateTime.now())){
return r;
}
//过期了,就通过独立线程 -> 互斥锁处理缓存击穿
String lockKey = RedisConstants.LOCK_SHOP_KEY + id;
if (!tryLock(lockKey)) {
log.debug("lockKey: " + lockKey + " Result: " + r);
CACHE_REBUILD_EXECUTOR.submit(()->{
try {
this.saveShop2Redis(id, dbFallback, keyPrefix, time, unit);
} catch (Exception e) {
throw new RuntimeException(e);
/*e.printStackTrace();*/
}finally {
//删除暂时的key, 释放互斥锁
stringRedisTemplate.delete(lockKey);
}
});
}
if(r==null){
return null;
}
return r;
}
public boolean tryLock(String key){
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", RedisConstants.LOCK_SHOP_TTL, TimeUnit.SECONDS);
//这里不可以直接返回flag,因为在拆箱过程中可能出现flag为null的情况;
return BooleanUtil.isTrue(flag);
}
public <ID, R> void saveShop2Redis(ID id,
Function<ID, R> dbFallback,
String keyPrefix,
Long time,
TimeUnit unit){
R r = dbFallback.apply(id);
setWithLogicalExpire(keyPrefix + id, r, time, unit);
}
}
总结
suc