Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ private CacheResult dispatchOperation(CacheContext context) {

/**
* 处理 GET 操作
*
*
* 注意:锁逻辑已由 SyncLockHandler 处理,这里直接执行 Redis 操作
*/
private CacheResult handleGet(CacheContext context) {
Expand All @@ -130,7 +130,7 @@ private CacheResult handleGet(CacheContext context) {
return CacheResult.miss();

} catch (Exception e) {
return errorHandler.handleError(context.getOperation(), context.getCacheName(), context.getRedisKey(), e);
return errorHandler.handleError(context.getOperation(), context.getCacheName(), e);
}
}

Expand Down Expand Up @@ -190,7 +190,7 @@ private CacheResult handlePut(CacheContext context) {
return CacheResult.success();

} catch (Exception e) {
return errorHandler.handleError(context.getOperation(), context.getCacheName(), context.getRedisKey(), e);
return errorHandler.handleError(context.getOperation(), context.getCacheName(), e);
}
}

Expand Down Expand Up @@ -230,7 +230,7 @@ private CacheResult handlePutIfAbsent(CacheContext context) {
return CacheResult.existing(null);

} catch (Exception e) {
return errorHandler.handleError(context.getOperation(), context.getCacheName(), context.getRedisKey(), e);
return errorHandler.handleError(context.getOperation(), context.getCacheName(), e);
}
}

Expand All @@ -254,7 +254,7 @@ private CacheResult handleRemove(CacheContext context) {
return CacheResult.success();

} catch (Exception e) {
return errorHandler.handleError(context.getOperation(), context.getCacheName(), context.getRedisKey(), e);
return errorHandler.handleError(context.getOperation(), context.getCacheName(), e);
}
}

Expand Down Expand Up @@ -317,7 +317,7 @@ private CacheResult handleClean(CacheContext context) {
? CacheResult.FailureKind.PARTIAL_CLEAN
: CacheResult.FailureKind.REDIS;
return errorHandler.handleError(
context.getOperation(), context.getCacheName(), keyPattern, failureKind, e);
context.getOperation(), context.getCacheName(), failureKind, e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@
@Slf4j
class BloomFilterConfig {

private final String keyPrefix;
private final int bitSize;
private final int hashFunctions;
private final int hashCacheSize;
private final String keyPrefix;
private final int bitSize;
private final int hashFunctions;
private final int hashCacheSize;

public BloomFilterConfig(
String keyPrefix, int bitSize, int hashFunctions, int hashCacheSize) {
this.keyPrefix = keyPrefix;
this.bitSize = Math.max(1, bitSize);
this.hashFunctions = Math.max(1, hashFunctions);
this.hashCacheSize = Math.max(1, hashCacheSize);
}
public BloomFilterConfig(
String keyPrefix, int bitSize, int hashFunctions, int hashCacheSize) {
this.keyPrefix = keyPrefix;
this.bitSize = Math.max(1, bitSize);
this.hashFunctions = Math.max(1, hashFunctions);
this.hashCacheSize = Math.max(1, hashCacheSize);
}

int[] positionsFor(String key) {
if (key == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
* <li>按 operation 调度策略 + 应用策略(日志级别 + CacheResult 形态)</li>
* </ol>
*
* <p>单一入口 {@link #handleError(CacheOperation, String, String, Exception)} +
* <p>单一入口 {@link #handleError(CacheOperation, String, Exception)} +
* per-operation 策略集中到 {@link #STRATEGIES} 不可变 Map。调用方只需传
* {@link CacheContext#getOperation() context.getOperation()},无需记忆具体方法名;
* 新增 operation 只需在 {@link CacheOperation} 追加枚举值 + 在 {@link #STRATEGIES} 追加一行。
Expand Down Expand Up @@ -123,8 +123,8 @@ public CacheErrorHandler(
/**
* 按 operation 调度错误策略并保留诊断信息(typed kind)。
*/
public CacheResult handleError(CacheOperation operation, String cacheName, String key, Exception e) {
return handleException(operation, cacheName, key, e, strategyFor(operation), classify(e));
public CacheResult handleError(CacheOperation operation, String cacheName, Exception e) {
return handleException(operation, cacheName, e, strategyFor(operation), classify(e));
}

/**
Expand All @@ -133,28 +133,18 @@ public CacheResult handleError(CacheOperation operation, String cacheName, Strin
CacheResult handleError(
CacheOperation operation,
String cacheName,
String key,
FailureKind failureKind,
Exception e) {
return handleException(operation, cacheName, key, e, strategyFor(operation), failureKind);
return handleException(operation, cacheName, e, strategyFor(operation), failureKind);
}

/**
* 直接应用指定策略,供测试和显式内部调用使用。
* 应用策略并完成分类、日志与单次计数 —— 链内唯一失败出口。策略与 typed kind 由上面的
* {@code handleError} 重载按 operation 选定,不再对外暴露显式策略入口。
*/
public CacheResult handleException(
CacheOperation operation,
String cacheName,
String key,
Exception e,
ErrorStrategy strategy) {
return handleException(operation, cacheName, key, e, strategy, classify(e));
}

private CacheResult handleException(
CacheOperation operation,
String cacheName,
String key,
Exception e,
ErrorStrategy strategy,
FailureKind failureKind) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,32 @@

/**
* 缓存操作输入参数(不可变)
*
*
* 包含请求的原始数据,在整个责任链中只读。
* 设计为 record 确保不可变性。
*/
record CacheInput(
/** 缓存操作类型 */
CacheOperation operation,

/** 缓存名称 */
String cacheName,

/** Redis 完整 key */
String redisKey,

/** 实际 key(去除前缀) */
String actualKey,

/** 缓存值(字节数组) */
@Nullable byte[] valueBytes,

/** 反序列化后的值 */
@Nullable Object deserializedValue,

/** TTL */
@Nullable Duration ttl,

/** 缓存操作配置 */
@Nullable CachePolicyView.Source cacheOperation
) implements CacheContext.InputView {
Expand Down Expand Up @@ -69,10 +69,10 @@ public static class Builder {
public Builder deserializedValue(Object value) { this.deserializedValue = value; return this; }
public Builder ttl(Duration ttl) { this.ttl = ttl; return this; }
public Builder cacheOperation(CachePolicyView.Source op) { this.cacheOperation = op; return this; }

public CacheInput build() {
return new CacheInput(
operation, cacheName, redisKey, actualKey,
operation, cacheName, redisKey, actualKey,
valueBytes, deserializedValue, ttl, cacheOperation
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@

/**
* JVM + Redis 双层布隆过滤器,优先使用 JVM 过滤结果,必要时回退 Redis。
*
*
* <p>设计说明:
* <ul>
* <li>本地布隆过滤器用于快速检查,减少 Redis 访问</li>
* <li>Redis 布隆过滤器作为权威数据源,在集群间共享</li>
* <li>缓存驱逐时需要调用 clear() 同步清除两个过滤器</li>
* </ul>
*
*
* <p>注意事项:
* <ul>
* <li>布隆过滤器本身存在误判率(false positive),这是预期行为</li>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@


import io.github.davidhlp.spring.cache.redis.chain.model.CachePolicyView;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
Expand Down Expand Up @@ -39,15 +40,16 @@
* <li><b>locality</b>:bloom + sync + load 协议 + 异常翻译规则全部内聚在一处文件,
* 无需在 {@code RedisProCache} 与若干 seam 间跳转</li>
* <li><b>testability</b>:orchestrator 仅依赖 {@link BloomGate} / {@link SyncSupport} /
* {@link SyncLockTimeout} + 3 callback(redisKey / doubleCheck / putAfterLoad);
* {@link SyncLockTimeout} + 3 个构造期绑定的 callback(redisKey / doubleCheck / putAfterLoad);
* 单测可零 RedisProCache fixture 验证决策分支({@code BloomShortCircuited} /
* {@code Loaded} / {@code LoadedWithWriteBackFailure} / {@code LoadFailed})</li>
* <li><b>leverage</b>:{@code RedisProCache.get(key, loader)} 主体仅 1 行委派 + switch 翻译</li>
* </ul>
*
* <p><b>callback 协议</b>:orchestrator 不继承 {@code RedisCache},因此需要 cache-specific 操作
* (key 派生 / 双检 / 写回)以 callback 形式由 {@code RedisProCache} 注入;writer 入口则直接
* 使用静态 {@link #readThrough} 并传入字节适配:
* (key 派生 / 双检 / 写回)以 callback 形式在构造期由 {@code RedisProCache} 绑定;三个 callback
* 均为必需,缺失属于装配错误,构造时用 {@link Objects#requireNonNull} 带参数名拒绝。
* writer 入口不构造本类,直接使用静态 {@link #readThrough} 并传入字节适配:
* <ul>
* <li>{@code Function<Object, String> redisKeyFn} — 派生 Redis key 用于 BloomGate 与 SyncSupport</li>
* <li>{@code Function<Object, Cache.ValueWrapper> doubleCheckFn} — 缓存读原语(走
Expand Down Expand Up @@ -125,98 +127,56 @@ public record LoadFailed<T>(Throwable cause) implements LoadOutcome<T> {
private final Function<Object, Cache.ValueWrapper> boundDoubleCheckFn;
private final BiConsumer<Object, Object> boundPutAfterLoad;

public LoaderOrchestrator(@Nullable BloomGate bloomGate,
@Nullable SyncSupport syncSupport,
@Nullable SyncLockTimeout syncLockTimeout) {
this(bloomGate, syncSupport, syncLockTimeout, null, null, null);
}

/**
* 生产构造:一次性绑定 cache-specific 回调,隐藏 loader 编排的 callback 组装细节。
* 生产构造:一次性绑定保护依赖与 cache-specific 回调,隐藏 loader 编排的 callback 组装细节。
*
* <p>3 个回调是 loader 路径的必需操作,构造期分别以 {@link Objects#requireNonNull} 校验,
* 缺失即抛带参数名的 {@link NullPointerException};可选保护依赖保持既有可空语义。
*/
LoaderOrchestrator(
@Nullable BloomGate bloomGate,
@Nullable SyncSupport syncSupport,
@Nullable SyncLockTimeout syncLockTimeout,
@Nullable Function<Object, String> redisKeyFn,
@Nullable Function<Object, Cache.ValueWrapper> doubleCheckFn,
@Nullable BiConsumer<Object, Object> putAfterLoad) {
Function<Object, String> redisKeyFn,
Function<Object, Cache.ValueWrapper> doubleCheckFn,
BiConsumer<Object, Object> putAfterLoad) {
this.bloomGate = bloomGate;
this.syncSupport = syncSupport;
this.syncLockTimeout = syncLockTimeout;
this.boundRedisKeyFn = redisKeyFn;
this.boundDoubleCheckFn = doubleCheckFn;
this.boundPutAfterLoad = putAfterLoad;
this.boundRedisKeyFn = Objects.requireNonNull(redisKeyFn, "redisKeyFn");
this.boundDoubleCheckFn = Objects.requireNonNull(doubleCheckFn, "doubleCheckFn");
this.boundPutAfterLoad = Objects.requireNonNull(putAfterLoad, "putAfterLoad");
}

/**
* 生产调用入口:使用构造期绑定的 callbacks 执行 loader 路径。
* 唯一实例入口:编排 loader 路径 — bloom 短路 → sync 路径(sync=true) → default
* 路径(同一协议,无锁);cache-specific 操作使用构造期绑定的回调。
*
* @param cacheName 缓存名
* @param loader Spring Cache loader
* @param key 用户缓存 key
* @param operation 方法级缓存 operation,可为 null
* @param cacheName 缓存名(供 BloomGate 区分 cache;非 key 派生)
* @param loader Spring Cache {@link Callable} loader
* @param key 缓存 key(用户传入的原始 key;由绑定的 redisKeyFn 派生 Redis key)
* @param operation 方法级策略视图(可为 null,视作「无增强属性」→ 不走 bloom / sync)
* @param <T> 加载结果类型
* @return 编排结果
*/
<T> LoadOutcome<T> orchestrate(
String cacheName,
Callable<T> loader,
Object key,
@Nullable CachePolicyView.Source operation) {
if (boundRedisKeyFn == null
|| boundDoubleCheckFn == null
|| boundPutAfterLoad == null) {
throw new IllegalStateException("LoaderOrchestrator callbacks are not bound");
}
return orchestrate(
cacheName,
boundRedisKeyFn,
boundDoubleCheckFn,
boundPutAfterLoad,
loader,
key,
operation);
}

/**
* 编排 loader 路径:bloom 短路 → sync 路径(sync=true) → default 路径(同一协议,无锁)。
*
* @param cacheName 缓存名(供 BloomGate 区分 cache;非 key 派生)
* @param redisKeyFn 派生 Redis key(callback;caller 传 {@code key -> super.createCacheKey(key)})
* @param doubleCheckFn 缓存读原语(callback;caller 传 {@code key -> super.get(key)};
* 含链 GET + null round-trip,无 metrics — metrics 在外层记)
* @param putAfterLoad load 成功后写回缓存的 callback(caller 传 {@code (k, v) -> put(k, v)}
* 闭包 → 走 override 保留 putTimer + putCounter 指标)
* @param loader Spring Cache {@link Callable} loader
* @param key 缓存 key(用户传入的原始 key;由 redisKeyFn 派生 Redis key)
* @param operation 方法级策略视图(可为 null,视作「无增强属性」→ 不走 bloom / sync)
* @param <T> 加载结果类型
* @return {@link LoadOutcome} 四态之一
*/
@SuppressWarnings("unchecked")
public <T> LoadOutcome<T> orchestrate(
<T> LoadOutcome<T> orchestrate(
String cacheName,
Function<Object, String> redisKeyFn,
Function<Object, Cache.ValueWrapper> doubleCheckFn,
BiConsumer<Object, Object> putAfterLoad,
Callable<T> loader,
Object key,
@Nullable CachePolicyView.Source operation) {

// 1) Bloom 短路检查 — caller 据 BloomShortCircuited 自增 miss counter
if (isBloomShortCircuited(cacheName, redisKeyFn.apply(key), operation)) {
if (isBloomShortCircuited(cacheName, boundRedisKeyFn.apply(key), operation)) {
return new BloomShortCircuited<>();
}

// 2) Sync 路径 — sync=true 且 SyncSupport 在场才走;否则降级 default 路径
if (operation != null && operation.isSync() && syncSupport != null) {
return executeSyncLoad(cacheName, redisKeyFn, doubleCheckFn, putAfterLoad,
loader, key, operation);
return executeSyncLoad(cacheName, loader, key, operation);
}

// 3) Default 路径 — 与 sync 路径同一 load 协议,只是不跑在分布式锁内
return executeLoad(cacheName, doubleCheckFn, putAfterLoad, loader, key);
return executeLoad(cacheName, loader, key);
}

/**
Expand All @@ -227,9 +187,6 @@ public <T> LoadOutcome<T> orchestrate(
*/
private <T> LoadOutcome<T> executeSyncLoad(
String cacheName,
Function<Object, String> redisKeyFn,
Function<Object, Cache.ValueWrapper> doubleCheckFn,
BiConsumer<Object, Object> putAfterLoad,
Callable<T> loader,
Object key,
CachePolicyView.Source operation) {
Expand All @@ -238,10 +195,10 @@ private <T> LoadOutcome<T> executeSyncLoad(
: SyncLockTimeout.Resolved.fromSeconds(
SyncLockTimeout.DEFAULT_LOCK_TIMEOUT_SECONDS);
try {
String lockKey = redisKeyFn.apply(key);
String lockKey = boundRedisKeyFn.apply(key);
return syncSupport.executeSync(
lockKey,
() -> executeLoad(cacheName, doubleCheckFn, putAfterLoad, loader, key),
() -> executeLoad(cacheName, loader, key),
timeout);
} catch (Throwable cause) {
return new LoadFailed<>(cause);
Expand All @@ -258,17 +215,15 @@ private <T> LoadOutcome<T> executeSyncLoad(
@SuppressWarnings("unchecked")
private <T> LoadOutcome<T> executeLoad(
String cacheName,
Function<Object, Cache.ValueWrapper> doubleCheckFn,
BiConsumer<Object, Object> putAfterLoad,
Callable<T> loader,
Object key) {
return readThrough(
cacheName,
() -> doubleCheckFn.apply(key),
() -> boundDoubleCheckFn.apply(key),
cached -> cached != null,
cached -> (T) cached.get(),
loader::call,
value -> putAfterLoad.accept(key, value),
value -> boundPutAfterLoad.accept(key, value),
cause -> translateCacheLoaderFailure(key, loader, cause));
}

Expand Down
Loading
Loading