简介
背景:系统有个高频的全局访问配置,该数据体积小,直接从 redis 取,怕高频访问 影响其他核心业务
例如:系统配置近乎不更改,且延迟更新允许可接受
单机:redis 查询并发,复杂类型大概3w,简单类型大概为10w+,若你的系统并发没到1w+,且没有并发要求,建议直接用redis得了,没必要改造;
这里封装了二级缓存服务方法,屏蔽了redis和本地的实现:TwoLevelCacheService
一、调用样例
1)记得编辑或删除,调用 remove 方法,
2)值获取:尽可能不要直接获取,尽量先判断是否存在该key
因为我这边实际业务是可能空列表,判断key,可避免空值一直访问数据库;若你的业务不存在空值一说,也可直接取数,然后根据值存不存在,再判断要不要访问数据库;
package com.server.common.config.service.impl;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.util.ObjectUtil;
import com.server.common.cache.service.TwoLevelCacheService;
import java.util.*;
@Slf4j
@Service
public class SysConfigServiceImpl implements SysConfigService {
@Resource
private TwoLevelCacheService twoLevelCacheService;
@Override
public SysConfigVo findById(Long id) {
.....
}
@Transactional
@Override
public void save() {
// 保存和、删除都得移除相应缓存
twoLevelCacheService.remove(RedisKeyConstants.DISABLE_PROCESS_KEY);
.....
}
@Override
public Boolean getDisableStatus() {
// 1 检查二级缓存是否存在该key (但值可能为空,所以检查key)
String cacheKey = RedisKeyConstants.DISABLE_PROCESS_KEY;
if (twoLevelCacheService.exists(cacheKey)) {
// 简单类型
return twoLevelCacheService.get(cacheKey, Boolean.class);
/** 对象复杂类型,使用下面方法获取值
return twoLevelCacheService.get(cacheKey, new TypeReference<>() {
});
**/
}
// 2 获取系统配置
SysConfigVo sysConfigVo = findById(1L);
Boolean disableStatus = ObjectUtil.isNotNull(sysConfigVo) ? sysConfigVo.getDisable() : Boolean.FALSE;
// 3 将状态加入二级缓存
twoLevelCacheService.set(cacheKey, disableStatus);
return disableStatus;
}
}

二 springboot 二级缓存封装的服务类
工具类的过期时间,可根据系统性质,问AI做调整
1、TwoLevelCacheService 二级缓存的服务;
2、LocalCacheManager 本地缓存的实现
3、RedisUtil 静态工具类;因为系统有些静态实现需要调用,这里没用服务的方式
2.1 TwoLevelCacheService
1)方法存在默认本地和redis缓存的过期时间,你可根据自己的业务类型做调整;
2)若你的是分布式业务,且在乎所有机器60s后本地缓存才生效,你可以使用redis广播改造remove方法;
注:若你的类型为list、map、set,你可继续新增优化方法,使用redis支持的list、map、set方法降低序列化造成的性能损耗’
package com.server.common.cache.service;
import cn.hutool.core.lang.TypeReference;
import com.server.common.cache.LocalCacheManager;
import com.server.common.util.RedisUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* 二级缓存服务(TwoLevelCacheService)
*
* <p>说明:
* - 结合 LocalCacheManager(本地)和 Redis(远程)实现二级缓存。
* - 读策略:先本地,未命中再 Redis(读透)。
* - 写策略:同时写本地和 Redis(写同步)。
* - 适用:小对象、热点数据;大对象请直接用 RedisUtil。
* - 本地缓存默认 1 分钟,Redis 默认 1 小时,可覆盖。
*/
@Slf4j
@Service
public class TwoLevelCacheService {
@Resource
private LocalCacheManager localCacheManager;
/**
* 默认 Redis 过期时间 1小时
*/
private static final long DEFAULT_REDIS_TIMEOUT = 3600;
/**
* 默认本地 1分钟
*/
private static final long DEFAULT_LOCAL_TIMEOUT = TimeUnit.SECONDS.toMillis(60);
// ================== 写入缓存 ==================
/**
* 写入缓存,使用默认本地和 Redis 过期时间
*
* @param key 缓存 key
* @param value 缓存值
* @param <T> 值类型
*/
public <T> void set(String key, T value) {
set(key, value, DEFAULT_LOCAL_TIMEOUT, DEFAULT_REDIS_TIMEOUT);
}
/**
* 写入缓存,自定义本地缓存和 Redis 过期时间
*
* @param key 缓存 key
* @param value 缓存值
* @param localTimeoutMs 本地缓存过期时间(毫秒)
* @param redisTimeoutSeconds Redis 缓存过期时间(秒)
* @param <T> 值类型
*/
public <T> void set(String key, T value, long localTimeoutMs, long redisTimeoutSeconds) {
// 1 空值也会放入本地的
localCacheManager.set(key, value, localTimeoutMs);
// 2 redis 缓存不接受空值
RedisUtil.set(key, value, redisTimeoutSeconds);
}
// ================== 获取缓存 ==================
/**
* 获取缓存(简单类型或非嵌套对象)
*
* @param key 缓存 key
* @param clazz 值类型
* @param <T> 类型
* @return 如果存在返回缓存值,否则返回 null
*/
public <T> T get(String key, Class<T> clazz) {
T value = localCacheManager.get(key, clazz);
if (value != null) {
log.info("走本地缓存key{},val{}", key, value);
return value;
}
value = RedisUtil.get(key, clazz);
if (value != null) {
log.info("走redis缓存key{},val{}", key, value);
localCacheManager.set(key, value);
}
return value;
}
/**
* 获取缓存(复杂对象 / 泛型集合)
*
* @param key 缓存 key
* @param typeReference 泛型类型引用
* @param <T> 类型
* @return 如果存在返回缓存值,否则返回 null
*/
public <T> T get(String key, TypeReference<T> typeReference) {
T value = localCacheManager.get(key, typeReference);
if (value != null) {
log.info("走本地缓存key{},val{}", key, value);
return value;
}
value = RedisUtil.get(key, typeReference);
if (value != null) {
log.info("走redis缓存key{},val{}", key, value);
localCacheManager.set(key, value);
}
return value;
}
// ================== 判断、删除缓存 ==================
/**
* 删除缓存
*
* @param key 缓存 key
*/
public boolean exists(String key) {
return localCacheManager.contains(key) || RedisUtil.exists(key);
}
/**
* 删除缓存
*
* @param key 缓存 key
*/
public void remove(String key) {
localCacheManager.remove(key);
RedisUtil.del(key);
}
// ================== 过期操作 ==================
/**
* 刷新缓存过期时间
*
* <p>
* 如果 key 存在,则同时刷新本地缓存和 Redis 缓存的 TTL。
* </p>
*
* @param key 缓存 key
* @param localTimeoutMs 本地缓存 TTL(毫秒)
* @param redisTimeoutSeconds Redis TTL(秒)
*/
public void expire(String key, long localTimeoutMs, long redisTimeoutSeconds) {
Object value = localCacheManager.get(key, Object.class);
if (value != null) {
localCacheManager.set(key, value, localTimeoutMs);
}
RedisUtil.expire(key, redisTimeoutSeconds);
}
}
2.2 LocalCacheManager
注:1)这里为了防止强制类型转换报错,简单类型存的原值,复杂类型存的序列化的json值;
2)由于hutool 对简单类型转json,直接变成{},所以也不能直接闭眼都存储json
package com.server.common.cache;
import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.TimedCache;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.json.JSONUtil;
import jakarta.annotation.PostConstruct;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;
/**
* 本地缓存管理器(LocalCacheManager)
*
* <p>说明:
* - 存储在 JVM 堆内存,用于加速热点数据访问。
* - 适用:小对象、热点数据,读多写少。
* - 不适用:大对象或全量列表,可能占用过多堆内存,建议直接使用 RedisUtil。
*
* <p>存储复杂对象统一 JSON,获取复杂对象请使用 TypeReference。
*/
@Component
public final class LocalCacheManager {
private TimedCache<String, Object> cache;
/**
* 默认缓存过期时间:60 秒
*/
private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(60);
@PostConstruct
private void init() {
cache = CacheUtil.newTimedCache(DEFAULT_TIMEOUT_MS);
}
// ================== 设置缓存 ==================
/**
* 使用默认过期时间
*/
public <T> void set(String key, T value) {
set(key, value, DEFAULT_TIMEOUT_MS);
}
/**
* 设置缓存
* 简单类型直接存储,复杂对象统一 JSON
*/
public <T> void set(String key, T value, long timeoutMs) {
if (value == null) {
cache.put(key, null, timeoutMs);
return;
}
Object storeValue;
if (isSimpleType(value.getClass())) {
storeValue = value;
} else {
storeValue = JSONUtil.toJsonStr(value);
}
cache.put(key, storeValue, timeoutMs);
}
// ================== 获取缓存 ==================
/**
* 获取简单类型或非嵌套对象
* <p>
* 注意:仅限基本类型或简单对象,复杂对象或集合请使用 get(String, TypeReference<T>)
*/
public <T> T get(String key, Class<T> clazz) {
Object value = cache.get(key, false);
if (value == null) return null;
if (isSimpleType(clazz)) {
return Convert.convertQuietly(clazz, value);
} else if (value instanceof String str) {
return JSONUtil.toBean(str, clazz, false);
} else {
return null;
}
}
/**
* 获取复杂对象 / 嵌套对象 / 泛型集合
* <p>
* 使用 TypeReference 确保嵌套泛型类型安全
*/
public <T> T get(String key, TypeReference<T> typeReference) {
Object value = cache.get(key, false);
if (value == null) return null;
if (value instanceof String str) {
return JSONUtil.toBean(str, typeReference, false);
} else {
return Convert.convert(typeReference.getType(), value);
}
}
// ================== 类型快捷 get ==================
public Boolean getBool(String key) {
return Convert.toBool(get(key, Boolean.class));
}
public String getString(String key) {
return Convert.toStr(get(key, String.class));
}
public Integer getInt(String key) {
return Convert.toInt(get(key, Integer.class));
}
public Long getLong(String key) {
return Convert.toLong(get(key, Long.class));
}
public BigDecimal getBigDecimal(String key) {
return Convert.toBigDecimal(get(key, BigDecimal.class));
}
// ================== 移除 / 判断 ==================
public void remove(String key) {
cache.remove(key);
}
public boolean contains(String key) {
return cache.containsKey(key);
}
public void pruneAll() {
cache.prune();
}
// ================== 内部方法 ==================
private boolean isSimpleType(Class<?> clazz) {
return clazz.isPrimitive() ||
clazz == Boolean.class ||
clazz == Byte.class ||
clazz == Character.class ||
clazz == Short.class ||
clazz == Integer.class ||
clazz == Long.class ||
clazz == Float.class ||
clazz == Double.class ||
clazz == String.class ||
clazz == BigDecimal.class;
}
}
2.3 RedisUtil
当然,像list、map、set,你可以改造工具类,使用redis的原生类型,进一步提高响应性能
注:1)由于hutool 简单类型转json,直接变成{},所以也不能直接闭眼都存储json
package com.server.common.util;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.json.JSONUtil;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.time.Duration;
import java.util.*;
@Slf4j
@Component
public class RedisUtil {
@Resource
private RedisTemplate<String, Object> redisTemplate;
// 静态初始化当前类
public static RedisUtil redisUtil;
@PostConstruct
public void init() {
redisUtil = this;
}
// ================== 存储 ==================
/**
* 赋值,永不过期
*
* @param key key
* @param value 值
*/
public static void set(String key, Object value) {
if (value == null) {
return;
}
// 不设置 TTL
Object storeValue = isSimpleType(value.getClass()) ? value : JSONUtil.toJsonStr(value);
redisUtil.redisTemplate.opsForValue().set(key, storeValue);
}
/**
* 存对象,自定义过期时间(秒)
*/
public static <T> void set(String key, T value, long timeoutSeconds) {
if (value == null) {
return;
}
Object storeValue = isSimpleType(value.getClass()) ? value : JSONUtil.toJsonStr(value);
redisUtil.redisTemplate.opsForValue().set(key, storeValue, Duration.ofSeconds(timeoutSeconds));
}
// ================== 取值(指定类型) ==================
/**
* 取值
*
* @param key 参数
* @return 值
*/
public static <T> T get(String key, Class<T> clazz) {
Object value = redisUtil.redisTemplate.opsForValue().get(key);
if (value == null) {
return null;
}
if (isSimpleType(clazz)) {
return Convert.convertQuietly(clazz, value);
}
if (value instanceof String str) {
return JSONUtil.toBean(str, clazz, false);
}
return null;
}
/**
* 获取复杂对象 / 嵌套对象 / 泛型集合
*/
public static <T> T get(String key, TypeReference<T> typeReference) {
Object value = redisUtil.redisTemplate.opsForValue().get(key);
if (value == null) {
return null;
}
if (value instanceof String str) {
return JSONUtil.toBean(str, typeReference, false);
}
return Convert.convert(typeReference.getType(), value);
}
// ================== 常用类型快捷 get ==================
public static String getString(String key) {
return get(key, String.class);
}
public static Boolean getBoolean(String key) {
return get(key, Boolean.class);
}
public static Integer getInteger(String key) {
return get(key, Integer.class);
}
public static Long getLong(String key) {
return get(key, Long.class);
}
public static BigDecimal getBigDecimal(String key) {
return get(key, BigDecimal.class);
}
// ================== 判断 / 删除 ==================
/**
* 判断某个 key 是否存在
*
* @param key key
* @return true 存在, false 不存在
*/
public static boolean exists(String key) {
return Boolean.TRUE.equals(redisUtil.redisTemplate.hasKey(key));
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
public static void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisUtil.redisTemplate.delete(key[0]);
} else {
redisUtil.redisTemplate.delete(ListUtil.of(key));
}
}
}
/**
* 删除缓存
*
* @param keys 删除多个
*/
public static void del(Collection<String> keys) {
redisUtil.redisTemplate.delete(keys);
}
// ================== 过期操作 ==================
/**
* 设置过期时间(秒)
*/
public static void expire(String key, long timeoutSeconds) {
if (!exists(key)) {
return;
}
redisUtil.redisTemplate.expire(key, Duration.ofSeconds(timeoutSeconds));
}
/**
* 获取值并刷新过期时间(秒)
*/
public static <T> T getAndExpire(String key, Class<T> clazz, long timeoutSeconds) {
T value = get(key, clazz);
if (value != null) {
expire(key, timeoutSeconds);
}
return value;
}
// ================== 注:基础的封装类型,不能序列化,否则 ==================
private static boolean isSimpleType(Class<?> clazz) {
return clazz.isPrimitive()
|| clazz == Boolean.class
|| clazz == Byte.class
|| clazz == Character.class
|| clazz == Short.class
|| clazz == Integer.class
|| clazz == Long.class
|| clazz == Float.class
|| clazz == Double.class
|| clazz == String.class
|| clazz == BigDecimal.class;
}
}
1378

被折叠的 条评论
为什么被折叠?



