在我们的项目中,经常会需要查询用户信息,比如前端页面需要展示,用户校验权限需要,可以说我们做的几乎所有操作都需要查询。
而这些用户信息,很多内容都是不变的,比如用户的昵称、手机号等,或者说是变化不频发的。所以这些数据我们就可以做缓存。
在缓存选择上,我们不仅用了分布式缓存,同时为了提升性能,还做了本地缓存。也就是说我们的一次查询,会先查询本地缓存,如果本地缓存查不到,再查询分布式缓存。并且在分布式缓存中查询到之后保存到本地缓存中一份。
public String query(String key){
String localResult = localCache.get(key);
if(localResult == null){
String remoteResult = remoteCache.get(key);
if(remoteResult != null){
localCache.put(remoteResult);
}
}
return localResult;
}
现在有一个缓存框架可以帮我们干这件事,我们选择用通用的二级缓存框架,阿里开源的 JetCache。
JetCache是一个基于java的缓存系统封装,提供统一的API和注解简化缓存的使用。 JetCache提供了比SpringCache更强大的注解,可以原生的支持TTL、两级缓存、分布式自动刷新,提供了Cache接口用于手动缓存操作。它支持多种缓存策略和缓存存储类型,包括本地缓存(如 Caffeine、Guava 等)和分布式缓存( Redis、Memcached 等)
我们以Caffeine和Redis为例,讲一下,如何接入进来。
1、引入依赖:
<!-- Caffeine -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
</dependency>
<!-- JetCache -->
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redisson</artifactId>
<version>2.7.5</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、编写相应的文件配置:
jetcache:
statIntervalMinutes: 1 # 设置统计信息收集的时间间隔为1分钟
areaInCacheName: false # 禁用在缓存名称中包含区域名称
local:
default:
type: caffeine # 使用Caffeine作为本地缓存实现
keyConvertor: fastjson2 # 使用Fastjson2进行键转换
remote:
default:
type: redisson # 使用Redisson作为远程缓存实现
keyConvertor: fastjson2 # 使用Fastjson2进行键转换
broadcastChannel: ${spring.application.name} # 广播频道名称设置为Spring应用程序名称
keyPrefix: ${spring.application.name} # 键前缀设置为Spring应用程序名称
valueEncoder: java # 使用Java进行值编码
valueDecoder: java # 使用Java进行值解码
defaultExpireInMillis: 5000 # 设置默认过期时间为5000毫秒(5秒)
3、开启注解
在启动类上增加@EnableMethodCache注解
@EnableMethodCache(basePackages = "org.h2cn")
public class DemoApplication {
}
接入后,我们在我们的用户查询接口中增加注解:
@Service
public class UserService extends ServiceImpl<UserMapper, User> implements InitializingBean {
@Autowired
private UserMapper userMapper;
/**
* 通过用户ID查询用户信息
*
* @param userId
* @return
*/
@Cached(name = ":user:cache:id:", expire = 3000, cacheType = CacheType.BOTH, key = "#userId", cacheNullValue = true)
@CacheRefresh(refresh = 60, timeUnit = TimeUnit.MINUTES)
public User findById(Long userId) {
return userMapper.findById(userId);
}
/**
* 更新用户信息
*
* @param userModifyRequest
* @return
*/
@CacheInvalidate(name = ":user:cache:id:", key = "#userModifyRequest.userId")
public Boolean modify(UserModifyRequest userModifyRequest) {
User user = userMapper.findById(userModifyRequest.getUserId());
Assert.notNull(user, () -> new UserException(USER_NOT_EXIST));
Assert.isTrue(user.canModifyInfo(), () -> new UserException(USER_STATUS_CANT_OPERATE));
if (StringUtils.isNotBlank(userModifyRequest.getNickName()) && nickNameExist(userModifyRequest.getNickName())) {
throw new UserException(NICK_NAME_EXIST);
}
BeanUtils.copyProperties(userModifyRequest, user);
if (StringUtils.isNotBlank(userModifyRequest.getPassword())) {
user.setPasswordHash(DigestUtil.md5Hex(userModifyRequest.getPassword()));
}
if (updateById(user)) {
addNickName(userModifyRequest.getNickName());
return true;
}
return false;
}
}
@Cached:为一个方法添加缓存,创建对应的缓存实例,注解可以添加在接口或者类中的方法上面,该类必须是spring bean
name:指定缓存实例名称,如果没有指定,会根据类名+方法名自动生成。
expire:超时时间。如果注解上没有定义,会使用全局配置,如果此时全局配置也没有定义,则为无穷大。
cacheType:缓存的类型,支持:REMOTE、LOCAL、BOTH,如果定义为BOTH,会使用LOCAL和REMOTE组合成两级缓存。
key:使用SpEL指定缓存key,如果没有指定会根据入参自动生成。
cacheNullValue:当方法返回值为null的时候是否要缓存。
@CacheRefresh:用于标识这个缓存需要自动刷新
refresh:刷新的时间间隔
timeUnit:时间单位
@CacheInvalidate:用于标识这个方法被调用时需要移除缓存
name:指定缓存的唯一名称,一般指向对应的@Cached定义的name。
key:使用SpEL指定key,如果没有指定会根据入参自动生成。
除此之外,JetCache 还提供了@CreateCache、@CacheUpdate等常用注解,这就是我们基于缓存的自动更新+失效,来保证缓存的一致性的一般做法。