文章目录
前言
现阶段公司后端架构中缓存模块代码大量冗余,各个服务都有各自的缓存模块,并且功能一致,由于之前没有合适的方案提取Client方法,因此一直没有进行优化
提示:以下是本篇文章正文内容,下面案例可供参考
一、技术沉淀
1.模板模式
(1)介绍
模板方法模式是一种行为设计模式, 它在父类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。主要要用来复用代码
(2)场景模拟
在我们的系统中把十三个重要服务比喻为一个商家,每个商家都会有一些营销活动,活动的时候赠送礼物,这十三个商家要用的赠礼都放在各自的仓库里。不过每个商家来拿货的时候,各家门口的保安都需要确定商家的身份。
(2)场景解析
在这个场景中,我们系统的每个服务就是商家,仓库就是对应的Redis缓存,而那个门口确定身份的保安就是请求Redis所需要的Key。在这个场景中每个微服务都需要去Redis缓存中拿数据,虽然不同的服务拿出来数据有可能不同,可是本质上来分析就是服务带着Key去请求Redis
二、需求结合代码
1. 分析
- 是否可以让所有的服务从
自己
去拿货改为定一个中间人
去拿货? - 如果拿货的那个角色可以让
中间人
去执行,那么仓库是否也可以统一管理
呢?因为不统一管理的话,中间人需要拿着拿着商家的钥匙跑遍不同的仓库,效率上会更加低下。 - 如何让
中间人拿到商家的身份卡
去通过保安的识别?
2. 代码实现
(1). 定义中间人
/**
* 中间人VO类,继承了[AbstractRedisGet]仓库出入权
* <T> 为泛型是因为为了适配各个服务数据
* @author 路过人间的姜先生
*/
@Getter
@Setter
public abstract class AbstractDefaultVo<T> extends AbstractRedisGet {
private String id;
private String createUserId;
private String updateUserId;
private String createTime;
private String updateTime;
}
/**
* @className: ObjectVo
* @description: 自定义函数接口
* @author: 路过人间的姜先生
* @date: 2022/9/1
*/
@FunctionalInterface
public interface ObjectVo<T> {
T get();
}
(2). 定义仓库
/**
* @className: AbstractRedisGet
* @description: 统一管理获取缓存的方法(缓存仓库入口)
* @author: 路过人间的姜先生
* @date: 2022/8/26
*/
@Slf4j
@Component
@Data
public abstract class AbstractRedisGet {
@JSONField(serialize = false)
private String redisKey;
// 需要各个服务VO去实现的 build()方法,把对应的 redisKey 在接口里面进行赋值
public abstract void build();
/**
* 获取Redis的方法
*
* @param: Supplier<T> supplier java8中自带的生产型接口
* @author: 路过人间的姜先生
* @date: 2022/8/26
*/
public <T> T getObjectVo(Supplier<T> supplier) {
initializeBuild();
Class aClass = parametBuilder();
if (aClass != null) {
// 获取缓中的对象
return getT(redisKey, aClass, null, supplier);
}
return null;
}
/**
* 获取Redis的方法,重载上面的获取Redis方法,在上面方法的基础上,
* 可以重新设置缓存失效时间
*
* @author: 路过人间的姜先生
* @date: 2022/8/26
*/
public <T> T getObjectVo(Integer seconds, Supplier<T> supplier) {
initializeBuild();
Class aClass = parametBuilder();
if (aClass != null) {
// 获取缓中的对象
return getT(redisKey, aClass, seconds, supplier);
}
return null;
}
/**
* 获取Redis的方法,其中只需要把需要执行的查询方法传入,就可以执行缓存查询的方法,如果缓存中没有的情况下会去执行您传入的方法
*
* <p>和之前方法不同在于,可以使用支持任意类对象
*
* <p>author: 路过人间的姜先生
*
* <p>2022/8/26
*/
public <T> T getObject(ObjectVo<T> supplier) {
initializeBuild();
// 获取缓中的对象
return getT(this.redisKey, Object.class, null, supplier);
}
/**
* 获取Redis的方法,其中只需要把需要执行的查询方法传入,就可以执行缓存查询的方法,如果缓存中没有的情况下会去执行您传入的方法
*
* <p>和之前方法不同在于,可以使用支持任意类对象 , 并且不需要实现改抽象类,可以用作扩展
*
* <p>author: 路过人间的姜先生
*
* <p>2022/8/26
*/
public <T> T getObject(Class clazz, Integer seconds, ObjectVo<T> supplier) {
initializeBuild();
// 获取缓中的对象
return getT(this.redisKey, clazz, seconds, supplier);
}
/**
* 通过key删除缓存
*
* @author: 路过人间的姜先生
* @date: 2022/8/26
*/
public void deleteCache() {
initializeBuild();
if (StringUtils.isNotBlank(redisKey) && RedisUtils.isCached(redisKey)) {
RedisUtils.expire(redisKey, 1);
}
}
/**
* 构建处理Build方法,并且获取泛型实例
*
* @return: void
* @author: 路过人间的姜先生
* @date: 2022/8/31
*/
private Class parametBuilder() {
if (StringUtils.isNotBlank(redisKey)) {
// 获取泛型实列
ParameterizedType ptype = (ParameterizedType) this.getClass().getGenericSuperclass();
Class clazz = (Class<T>) ptype.getActualTypeArguments()[0];
return clazz;
}
return null;
}
/**
* 构建处理Build方法
*
* @date: 2022/9/1
*/
private void initializeBuild() {
// 处理build方法,获取到 redisKey
try {
Method build = this.getClass().getMethod("build");
build.invoke(this);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
log.error("Vo对象中的build方法不存在");
}
}
/**
* 返回缓存中的对象
*
* @author: 路过人间的姜先生
* @date: 2022/8/30
*/
private <T> T getT(String key, Class c, Integer seconds, Supplier<T> supplier) {
try {
return (T)
Optional.ofNullable(RedisUtils.getBean(key, c))
.map(
e -> {
RedisUtils.expire(key, seconds != null ? seconds : 86400);
return e;
})
.orElseGet(
() ->
Optional.ofNullable(get(supplier))
.map(
e -> {
RedisUtils.saveBean(key, e, seconds != null ? seconds : 86400);
return e;
})
.orElseGet(() -> null));
} catch (Exception e) {
log.error("查询缓存失败={}", e.getMessage());
return null;
}
}
/**
* 返回缓存中的对象 == 支持java基本对象类型
*
* @author: 路过人间的姜先生
* @date: 2022/8/30
*/
public <T> T getT(String key, Class c, Integer seconds, ObjectVo<T> supplier) {
try {
return (T)
Optional.ofNullable(RedisUtils.getBean(key, c))
.map(
e -> {
RedisUtils.expire(key, seconds != null ? seconds : 86400);
return e;
})
.orElseGet(
() ->
Optional.ofNullable(supplier.get())
.map(
e -> {
RedisUtils.saveBean(key, e, seconds != null ? seconds : 86400);
return e;
})
.orElseGet(() -> null));
} catch (Exception e) {
log.error("查询缓存失败={}", e.getMessage());
return null;
}
}
/**
* 单独处理查询服务的逻辑,并且进行异常处理
*
* @author: 路过人间的姜先生
* @date: 2022/8/26
*/
private <T> T get(Supplier<T> supplier) {
try {
return supplier.get();
} catch (Exception e) {
log.error("查询服务失败={}", e.getMessage());
}
return null;
}
}
三、最终实现效果
1. User服务配置中间人管理RedisKey:
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class UserVo extends AbstractDefaultVo<UserVo>{
public UserVo(String id) {
super.setId(id);
}
@Override
public void build() {
// 定义Rediskey , 在对对象进行初始化并且传入ID的时候,会直接给XcbRedisUtils的redisKey字段赋值
super.setRedisKey(UserRedisKey.USER_USERVO.getName() + super.getId());
}
}
2. 最终使用
// 查询缓存
UserVo userVo =
new UserVo(userId)
.getObjectVo(() -> {查询方法});
// 删除缓存
new UserVo(userId).deleteCache();
// 自定义查询缓存方法
AbstractRedisGet redisGet =
new AbstractRedisGet() {
@Override
public void build() {}
};
List<任意对象> list =
redisGet.getT(
key,
List.class,
15,
() -> {查询方法});
总结
代码架构优化是一个剥丝抽茧的过程,希望我的帖子对各位有帮助。