多级缓存可降低系统响应时间,并且降低二级缓存的压力。
本文采用的二级缓存是 Redis、一级缓存是 Caffeine
多级缓存设计
- 声明一个
LocalCache注解,标识此方法需要多级缓存 - 自定义一个
CaffeineRedisCache多级缓存对象 - 自定义CacheResolver缓存解析器:将
@Cacheable、@CachePut、@CacheEvict等注解中的信息解析为CaffeineRedisCache
多级缓存业务流程图
声明LocalCache注解
/**
* Cacheable和CacheEvict必须配置一致,如TTL一下,本地缓存配置一致
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface LocalCache {
/**
* 仅使用本地缓存
*/
boolean onlyLocal() default false;
}
自定义CaffeineRedisCache
@Getter
@Setter
public class CaffeineRedisCache extends AbstractValueAdaptingCache {
/**
* 一级缓存,若启用一级缓存则设置一个非空实例,反之则设置为null
*/
protected Cache firstCache;
/**
* 二级缓存
*/
protected Cache secondCache;
/**
* Create an {@code AbstractValueAdaptingCache} with the given setting.
*
* @param allowNullValues whether to allow for {@code null} values
*/
public CaffeineRedisCache(boolean allowNullValues) {
super(allowNullValues);
}
@Override
protected Object lookup(Object key) {
ValueWrapper valueWrapper = null;
if (firstCache != null) {
// 若启用了本地缓存则先从本地缓存查询
valueWrapper = firstCache.get(key);
}
if (valueWrapper == null) {
if (secondCache == null) {
return null;
}
valueWrapper = secondCache.get(key);
// 若二级缓存存在,但一级缓存被启用并且不存在key,则put到一级缓存
if (valueWrapper != null) {
if (firstCache != null) {
firstCache.put(key, valueWrapper.get());
}
} else {
return null;
}
}
return valueWrapper.get();
}
@Override
public String getName() {
if (firstCache != null) {
return firstCache.getName();
}
if (secondCache != null) {
return secondCache.getName();
}
throw new RuntimeException("Please provide a valid cache.");
}
@Override
public Object getNativeCache() {
if (firstCache != null) {
return firstCache.getNativeCache();
}
if (secondCache != null) {
return secondCache.getNativeCache();
}
return null;
}
@Override
public <T> T get(Object key, Callable<T> valueLoader) {
if (firstCache != null) {
T t = firstCache.get(key, valueLoader);
if (t != null) {
return t;
}
}
if (secondCache != null) {
return secondCache.get(key, valueLoader);
}
return null;
}
@Override
public void put(Object key, Object value) {
if (firstCache != null) {
firstCache.put(key, value);
}
if (secondCache != null) {
secondCache.put(key, value);
}
}
@Override
public void evict(Object key) {
if (firstCache != null) {
firstCache.evict(key);
}
if (secondCache != null) {
secondCache.evict(key);
}
}
@Override
public void clear() {
if (firstCache != null) {
firstCache.clear();
}
if (secondCache != null) {
secondCache.clear();
}
}
}
自定义CacheResolver
public class MultipleCacheResolver implements CacheResolver {
private final CustomRedisCacheManager redisCacheManager;
private final CaffeineCacheManager caffeineCacheManager;
/**
* 相同过期策略可使用同一个Caffeine
*/
private final Map<Long, Caffeine<Object, Object>> ttlCaffeineMap = new ConcurrentHashMap<>(64);
/**
* Cache无需重复创建,缓存相同CacheName的Cache对象
*/
private final Map<String, CaffeineCache> caffeineTTLCacheMap = new ConcurrentHashMap<>(64);
public MultipleCacheResolver(CustomRedisCacheManager redisCacheManager, CaffeineCacheManager caffeineCacheManager) {
this.redisCacheManager = redisCacheManager;
this.caffeineCacheManager = caffeineCacheManager;
}
@Override
@NotNull
public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
Collection<String> cacheNames = context.getOperation().getCacheNames();
Class<?> targetClazz = context.getTarget().getClass();
if (CollectionUtils.isEmpty(cacheNames)) {
return Collections.emptyList();
}
long clazzTTL = -1;
if (targetClazz.isAnnotationPresent(CacheTTL.class)) {
CacheTTL cacheTTL = targetClazz.getAnnotation(CacheTTL.class);
if (cacheTTL != null) {
clazzTTL = cacheTTL.value();
}
}
// FIXME: 缓存注解和驱逐缓存的TTL配置和LocalCache配置不一致时可能会取到不同的Cache导致数据异常
Method method = context.getMethod();
Collection<Cache> result = new ArrayList<>(cacheNames.size());
for (String cacheName : cacheNames) {
CaffeineRedisCache caffeineRedisCache = new CaffeineRedisCache(true);
boolean isOnlyLocalCache = false;
// 启用本地缓存
if (method.isAnnotationPresent(LocalCache.class)) {
LocalCache localCache = method.getAnnotation(LocalCache.class);
isOnlyLocalCache = localCache.onlyLocal(); // 是否只使用本地缓存
if (method.isAnnotationPresent(CacheTTL.class)) { // 有TTL
CacheTTL cacheTTL = context.getMethod().getAnnotation(CacheTTL.class);
clazzTTL = cacheTTL.value();
}
if (clazzTTL > 0) {
// 相同的TTL不必重复创建Caffeine, 取同一个即可
Caffeine<Object, Object> caffeine = ttlCaffeineMap.computeIfAbsent(clazzTTL, key -> Caffeine.newBuilder().expireAfterAccess(key, TimeUnit.SECONDS));
// (TTL, Caffeine)
// (CacheName, CaffeineCache)
// 缓存cacheName -> CaffeineCache
// 若同一个CacheName,不同的TTL,这里是有问题的,key应该是cacheName + TTL
CaffeineCache caffeineCache = caffeineTTLCacheMap.computeIfAbsent(cacheName + clazzTTL, k -> new CaffeineCache(cacheName, caffeine.build(), true));
caffeineRedisCache.setFirstCache(caffeineCache);
}
// 若已设置有TTL的Cache则不使用CaffeineManager的Cache对象
if (caffeineRedisCache.getFirstCache() == null) {
Cache caffeineCache = caffeineCacheManager.getCache(cacheName);
if (caffeineCache == null) {
throw new IllegalArgumentException("Cannot find cache named '" +
cacheName + "' for " + context.getOperation());
}
caffeineRedisCache.setFirstCache(caffeineCache);
}
}
// 非仅本地缓存,则需要创建redis二级缓存
if (!isOnlyLocalCache) {
Cache redisCache = redisCacheManager.getCache(cacheName);
if (redisCache == null) {
throw new IllegalArgumentException("Cannot find cache named '" +
cacheName + "' for " + context.getOperation());
}
caffeineRedisCache.setSecondCache(redisCache);
}
result.add(caffeineRedisCache);
}
return result;
}
}
将自定义CacheResolver注册为默认cacheResolver
/**
* 自定义默认cache resolver和 cacheManager
*/
@Configuration
public class CustomSpringCacheConfig implements CachingConfigurer {
@Resource
CaffeineCacheManager caffeineCacheManager;
@Resource
CustomRedisCacheManager redisCacheManager;
/**
* 多级缓存解析器
*/
@Override
public CacheResolver cacheResolver() {
return new MultipleCacheResolver(redisCacheManager, caffeineCacheManager);
}
/**
* 默认使用redis缓存
*/
@Override
public CacheManager cacheManager() {
return redisCacheManager;
}
}
实战应用
@Service
// SpringCache缓存框架的缓存机制类似于Map<String, Cache>, key是cacheName, Cache(类似于一个Map<String, Object>)是一个缓存对象
@CacheConfig(cacheNames = "user_role")
@CacheTTL(7 * 24 * 60 * 60L) // 设置TTL过期时间
public class UserRoleServiceImpl extends AbstractService<UserRoleDO, UserRoleOutputDTO, PageDTO> implements IUserRoleService {
@LocalCache
@Cacheable(key = "'user:' + #p0", unless = "#result == null")
public List<String> listRoleIdByUserId(String userId) {
return baseMapper.wrapper().eq(UserRoleDO::getUserId, userId).list().stream().map(UserRoleDO::getRoleId).distinct().toList();
}
@LocalCache
@Cacheable(key = "'role:' + #p0", unless = "#result == null")
public List<String> listUserIdByRoleId(String roleId) {
return baseMapper.wrapper().eq(UserRoleDO::getRoleId, roleId).list().stream().map(UserRoleDO::getUserId).distinct().toList();
}
@LocalCache
@CacheEvict(key = "'user:' + #p0")
public void deleteRoleIdListByUserId(String userId) {
baseMapper.wrapper().eq(UserRoleDO::getUserId, userId).delete();
}
@LocalCache
@CacheEvict(key = "'role:' + #p0")
public void evictUserIdListByRoleId(String roleId) { }
}
以上代码来源: 后端代码:github.com/L1yp/van-te…
点击链接加入群聊:【Van交流群】