mirror of
https://gitee.com/dromara/dax-pay
synced 2026-08-12 07:25:39 +08:00
feat(cache): 二级缓存支持开关,总开关全关 + L1 可单独关闭
- Cache.enabled 总开关:默认 true,关则 L1+L2 一并 NoOp(穿透到方法) - Cache.L1.enabled 单独开关:默认 true,总开关开时可关 L1 留 L2(纯 Redis 模式) - MultiLevelCache 按 cacheEnabled/l1Enabled 分发读写删;L1 关闭时广播仍发,兼容集群混合配置 - CachingConfiguration 启动日志输出当前缓存模式,便于排查
This commit is contained in:
@@ -144,13 +144,23 @@ public class CachingConfiguration implements CachingConfigurer {
|
||||
CacheInvalidationPublisher publisher,
|
||||
SecureCacheNameMatcher secureCacheNameMatcher,
|
||||
ObjectProvider<SecureAesGcmEncryptor> encryptorProvider) {
|
||||
// 缓存总开关:关闭后 L1+L2 一并退化成 NoOp(直接穿透到方法),用于排查/诊断
|
||||
boolean cacheEnabled = platformCommonProperties.getCache().isEnabled();
|
||||
// L1 单独开关:总开关开启时,可单独关闭 L1 仅保留 L2 Redis(纯 Redis 模式)
|
||||
boolean l1Enabled = platformCommonProperties.getCache().getL1().isEnabled();
|
||||
boolean secureL2Enabled = encryptorProvider.getIfAvailable() != null;
|
||||
log.info("缓存模式: enabled={}, l1={} -> {}",
|
||||
cacheEnabled, l1Enabled,
|
||||
!cacheEnabled ? "DISABLED (NoOp, 直接穿透)"
|
||||
: (l1Enabled ? "L1(Caffeine)+L2(Redis) 二级缓存" : "L2(Redis) only (L1 已关闭)"));
|
||||
return new MultiLevelCacheManager(
|
||||
redisCacheManager,
|
||||
localCacheRegistry,
|
||||
publisher,
|
||||
secureCacheNameMatcher,
|
||||
secureL2Enabled
|
||||
secureL2Enabled,
|
||||
cacheEnabled,
|
||||
l1Enabled
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import java.util.concurrent.Callable;
|
||||
/// - 缓存失效通过 Artemis 广播通知其他节点删除本地 L1
|
||||
/// - 本地缓存 key 必须统一使用字符串形式,保证跨节点广播删除一致性
|
||||
/// - 敏感缓存(secure:)在数据加密未启用时仅使用 L1,禁止明文写 Redis
|
||||
/// - 两层开关:[#cacheEnabled] 总开关关 L1+L2 一并 NoOp;[#l1Enabled] 在总开关开启时可单独关 L1 留 L2
|
||||
@Slf4j
|
||||
public class MultiLevelCache implements Cache {
|
||||
|
||||
@@ -34,19 +35,35 @@ public class MultiLevelCache implements Cache {
|
||||
/// 敏感缓存是否允许写/读 L2(依赖数据加密已启用)
|
||||
private final boolean secureL2Enabled;
|
||||
|
||||
/// 缓存总开关,false 时 L1+L2 一并 NoOp(直接穿透到方法)
|
||||
///
|
||||
/// 由 [cn.daxpay.open.platform.common.config.properties.PlatformCommonProperties.Cache#isEnabled] 控制。
|
||||
private final boolean cacheEnabled;
|
||||
|
||||
/// L1 本地缓存单独开关,false 时跳过 L1 读写删,仅保留 L2 Redis(纯 Redis 模式)
|
||||
///
|
||||
/// 由 [cn.daxpay.open.platform.common.config.properties.PlatformCommonProperties.Cache.L1#isEnabled] 控制。
|
||||
/// 仅在 [#cacheEnabled] 为 true 时有意义;总开关关闭时 L1 随之关闭。
|
||||
private final boolean l1Enabled;
|
||||
|
||||
public MultiLevelCache(String name,
|
||||
LocalCacheRegistry localCacheRegistry,
|
||||
Cache redisCache,
|
||||
CacheInvalidationPublisher publisher,
|
||||
boolean secureCache,
|
||||
boolean secureL2Enabled) {
|
||||
boolean secureL2Enabled,
|
||||
boolean cacheEnabled,
|
||||
boolean l1Enabled) {
|
||||
this.name = name;
|
||||
this.localCache = localCacheRegistry.getOrCreate(name);
|
||||
this.redisCache = redisCache;
|
||||
this.publisher = publisher;
|
||||
this.secureCache = secureCache;
|
||||
this.secureL2Enabled = secureL2Enabled;
|
||||
if (secureCache && !secureL2Enabled) {
|
||||
this.cacheEnabled = cacheEnabled;
|
||||
this.l1Enabled = l1Enabled;
|
||||
// 仅在缓存启用时才提示敏感缓存降级,关闭态不打扰日志
|
||||
if (cacheEnabled && secureCache && !secureL2Enabled) {
|
||||
log.warn("敏感缓存 [{}] 因未启用数据加密,仅使用 L1 本地缓存,不写 Redis", name);
|
||||
}
|
||||
}
|
||||
@@ -69,29 +86,40 @@ public class MultiLevelCache implements Cache {
|
||||
/// 读取缓存,优先从 L1 本地缓存读取,未命中则从 L2 Redis 读取并回填 L1
|
||||
///
|
||||
/// 读取流程:
|
||||
/// - 查 L1 本地缓存
|
||||
/// - L1 未命中则查 L2 Redis
|
||||
/// - L2 命中则回填 L1
|
||||
/// - 总开关关闭时直接返回 null(穿透到方法)
|
||||
/// - L1 开启时查本地缓存,命中直接返回
|
||||
/// - L1 关闭或未命中则查 L2 Redis
|
||||
/// - L2 命中且 L1 开启则回填 L1
|
||||
/// - 全未命中返回 null
|
||||
///
|
||||
/// 敏感缓存且未启用加密时跳过 L2,仅查 L1
|
||||
/// 敏感缓存且未启用加密时跳过 L2,仅查 L1(L1 也关则穿透)
|
||||
@Override
|
||||
public ValueWrapper get(Object key) {
|
||||
// 缓存总开关关闭:直接穿透,不查任何一层
|
||||
if (!this.cacheEnabled) {
|
||||
return null;
|
||||
}
|
||||
String localKey = this.toLocalKey(key);
|
||||
Object localValue = this.localCache.getIfPresent(localKey);
|
||||
if (localValue != null) {
|
||||
return () -> localValue;
|
||||
// L1 单独开关:开启时先查本地缓存
|
||||
if (this.l1Enabled) {
|
||||
Object localValue = this.localCache.getIfPresent(localKey);
|
||||
if (localValue != null) {
|
||||
return () -> localValue;
|
||||
}
|
||||
}
|
||||
|
||||
// 敏感缓存未开加密:禁止读 Redis,避免历史明文或无法解密的脏数据
|
||||
if (this.isL1Only()) {
|
||||
log.debug("敏感缓存 L1-only 未命中: cacheName={}, key={}", this.name, key);
|
||||
log.debug("敏感缓存 L1-only 未命中: cacheName={}, key={}, l1Enabled={}", this.name, key, this.l1Enabled);
|
||||
return null;
|
||||
}
|
||||
|
||||
ValueWrapper redisValue = this.redisCache.get(key);
|
||||
if (redisValue != null) {
|
||||
this.localCache.put(localKey, Objects.requireNonNull(redisValue.get()));
|
||||
// L1 开启才回填本地,关闭时仅返回 L2 值
|
||||
if (this.l1Enabled) {
|
||||
this.localCache.put(localKey, Objects.requireNonNull(redisValue.get()));
|
||||
}
|
||||
return redisValue;
|
||||
}
|
||||
|
||||
@@ -127,51 +155,77 @@ public class MultiLevelCache implements Cache {
|
||||
}
|
||||
}
|
||||
|
||||
/// 写入缓存,同时写入 L2 Redis 和 L1 本地缓存
|
||||
/// 写入缓存
|
||||
///
|
||||
/// 注意:写入操作不广播通知其他节点,其他节点在读取时会从 L2 加载最新值
|
||||
///
|
||||
/// 敏感缓存且未启用加密时仅写 L1,禁止明文写 Redis
|
||||
/// - L1 开启:写 L2 Redis + L1 本地
|
||||
/// - L1 关闭:仅写 L2 Redis(纯 Redis 模式)
|
||||
/// - 敏感缓存且未启用加密时仅写 L1(L1 也关则不缓存)
|
||||
@Override
|
||||
public void put(Object key, Object value) {
|
||||
// 缓存总开关关闭:直接穿透,不写任何一层
|
||||
if (!this.cacheEnabled) {
|
||||
return;
|
||||
}
|
||||
if (value == null) {
|
||||
log.debug("缓存值为空,跳过写入: cacheName={}, key={}", this.name, key);
|
||||
return;
|
||||
}
|
||||
String localKey = this.toLocalKey(key);
|
||||
if (this.isL1Only()) {
|
||||
this.localCache.put(localKey, value);
|
||||
log.debug("写入 L1-only 敏感缓存: cacheName={}, key={}", this.name, key);
|
||||
// 敏感缓存禁 Redis:仅写 L1;L1 也关则该缓存实质不缓存
|
||||
if (this.l1Enabled) {
|
||||
this.localCache.put(localKey, value);
|
||||
log.debug("写入 L1-only 敏感缓存: cacheName={}, key={}", this.name, key);
|
||||
} else {
|
||||
log.debug("敏感缓存 L1 已关闭且禁写 Redis,跳过写入: cacheName={}, key={}", this.name, key);
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.redisCache.put(key, value);
|
||||
this.localCache.put(localKey, value);
|
||||
log.debug("写入二级缓存: cacheName={}, key={}", this.name, key);
|
||||
if (this.l1Enabled) {
|
||||
this.localCache.put(localKey, value);
|
||||
}
|
||||
log.debug("写入缓存: cacheName={}, key={}, l1Enabled={}", this.name, key, this.l1Enabled);
|
||||
}
|
||||
|
||||
/// 删除缓存,同时删除 L2 Redis 和 L1 本地缓存,并广播通知其他节点删除 L1
|
||||
/// 删除缓存
|
||||
///
|
||||
/// 删除流程:
|
||||
/// - 删除 L2 Redis
|
||||
/// - 删除本机 L1
|
||||
/// - 发布 Artemis 广播消息
|
||||
/// - 其他节点收到消息后删除各自 L1
|
||||
/// - 删除 L2 Redis(共享层)
|
||||
/// - L1 开启时删除本机 L1
|
||||
/// - 发布 Artemis 广播消息(始终发,其他节点可能 L1 开启)
|
||||
///
|
||||
/// 注意:广播不因本机 L1 关闭而跳过——集群中其他节点可能 L1 开启,仍需通知其删除本地缓存
|
||||
@Override
|
||||
public void evict(Object key) {
|
||||
// 缓存总开关关闭:无缓存可删,直接返回
|
||||
if (!this.cacheEnabled) {
|
||||
return;
|
||||
}
|
||||
String localKey = this.toLocalKey(key);
|
||||
// 仍尝试删 Redis,清理可能存在的历史数据
|
||||
this.redisCache.evict(key);
|
||||
this.localCache.invalidate(localKey);
|
||||
log.debug("删除二级缓存: cacheName={}, key={}", this.name, key);
|
||||
if (this.l1Enabled) {
|
||||
this.localCache.invalidate(localKey);
|
||||
}
|
||||
log.debug("删除缓存: cacheName={}, key={}, l1Enabled={}", this.name, key, this.l1Enabled);
|
||||
this.publisher.publishEvict(this.name, localKey);
|
||||
}
|
||||
|
||||
/// 清空缓存,同时清空 L2 Redis 和 L1 本地缓存,并广播通知其他节点清空 L1
|
||||
/// 清空缓存
|
||||
///
|
||||
/// 清空 L2 Redis;L1 开启时清空本机 L1;始终广播(其他节点可能 L1 开启)
|
||||
@Override
|
||||
public void clear() {
|
||||
// 缓存总开关关闭:无缓存可清,直接返回
|
||||
if (!this.cacheEnabled) {
|
||||
return;
|
||||
}
|
||||
this.redisCache.clear();
|
||||
this.localCache.invalidateAll();
|
||||
log.debug("清空二级缓存: cacheName={}", this.name);
|
||||
if (this.l1Enabled) {
|
||||
this.localCache.invalidateAll();
|
||||
}
|
||||
log.debug("清空缓存: cacheName={}, l1Enabled={}", this.name, this.l1Enabled);
|
||||
this.publisher.publishClear(this.name);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,8 +18,9 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
///
|
||||
/// 设计要点:
|
||||
/// - 作为 Spring 默认 CacheManager,通过 @Primary 标识
|
||||
/// - L1 本地缓存始终启用,不需要开关控制
|
||||
/// - 缓存失效通知通过 Artemis 广播,始终启用
|
||||
/// - 两层开关:[#cacheEnabled] 总开关(关=L1+L2 全关);[#l1Enabled] L1 单独开关(总开关开时可关 L1 留 L2)
|
||||
/// - L2 无单独开关:关 L2 等同于关总开关
|
||||
/// - 缓存失效通知通过 Artemis 广播
|
||||
/// - 敏感 cacheName 结合 secureL2Enabled 决定是否写 Redis
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@@ -36,6 +37,17 @@ public class MultiLevelCacheManager implements CacheManager {
|
||||
/// 敏感缓存是否允许 L2(数据加密已启用时为 true)
|
||||
private final boolean secureL2Enabled;
|
||||
|
||||
/// 缓存总开关,false 时 L1+L2 一并 NoOp(直接穿透到方法)
|
||||
///
|
||||
/// 由 [cn.daxpay.open.platform.common.config.properties.PlatformCommonProperties.Cache#isEnabled] 透传。
|
||||
private final boolean cacheEnabled;
|
||||
|
||||
/// L1 本地缓存单独开关,false 时跳过 L1 仅保留 L2 Redis
|
||||
///
|
||||
/// 由 [cn.daxpay.open.platform.common.config.properties.PlatformCommonProperties.Cache.L1#isEnabled] 透传。
|
||||
/// 仅在 [#cacheEnabled] 为 true 时有意义。
|
||||
private final boolean l1Enabled;
|
||||
|
||||
private final Map<String, MultiLevelCache> cacheMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
@@ -58,14 +70,17 @@ public class MultiLevelCacheManager implements CacheManager {
|
||||
}
|
||||
|
||||
boolean secureCache = this.secureMatcher != null && this.secureMatcher.matches(name);
|
||||
log.debug("创建二级缓存: name={}, secure={}, secureL2Enabled={}", name, secureCache, this.secureL2Enabled);
|
||||
log.debug("创建二级缓存: name={}, secure={}, secureL2Enabled={}, cacheEnabled={}, l1Enabled={}",
|
||||
name, secureCache, this.secureL2Enabled, this.cacheEnabled, this.l1Enabled);
|
||||
return new MultiLevelCache(
|
||||
name,
|
||||
this.localCacheRegistry,
|
||||
redisCache,
|
||||
this.publisher,
|
||||
secureCache,
|
||||
this.secureL2Enabled
|
||||
this.secureL2Enabled,
|
||||
this.cacheEnabled,
|
||||
this.l1Enabled
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,12 @@ public class PlatformCommonProperties {
|
||||
///
|
||||
@Data
|
||||
public static class Cache {
|
||||
/// 缓存总开关,关闭后 L1 本地缓存 + L2 Redis 缓存全部退化成 NoOp(直接穿透到方法)
|
||||
///
|
||||
/// 设计要点:
|
||||
/// - L1 与 L2 是强绑定的整体(L1 加速 + L2 共享),不单独开关
|
||||
/// - true(默认)= 启用整套二级缓存;false = 彻底禁用,排查/诊断时使用
|
||||
private boolean enabled = true;
|
||||
/// L1 本地缓存配置
|
||||
private L1 l1 = new L1();
|
||||
/// L2 Redis 缓存配置
|
||||
@@ -40,6 +46,13 @@ public class PlatformCommonProperties {
|
||||
///
|
||||
@Data
|
||||
public static class L1 {
|
||||
/// L1 本地缓存单独开关,false 时关闭 L1 仅保留 L2 Redis(纯 Redis 模式)
|
||||
///
|
||||
/// 设计要点:
|
||||
/// - 仅在 [Cache#isEnabled] 总开关开启时生效;总开关关闭时 L1/L2 一并 NoOp
|
||||
/// - L2 无单独开关:关 L2 等同于关 [Cache#isEnabled] 总开关(全关)
|
||||
/// - 关闭 L1 适用场景:强一致性要求 / 排查本地脏读 / 单机无本地加速诉求
|
||||
private boolean enabled = true;
|
||||
/// 默认超时时间 60秒
|
||||
private Long defaultTtl = 60L;
|
||||
/// 默认最大容量
|
||||
|
||||
Reference in New Issue
Block a user