多级缓存(L1 + L2)的统一业务门面,注入即用。命名空间:L2Cache.Abstractions。
public interface ICacheClient<TKey, TValue> where TKey : notnull
{
string CacheName { get; }
// ---- 单条操作 ----
Task<TValue?> GetAsync(TKey key, CancellationToken cancellationToken = default);
Task<TValue?> GetOrLoadAsync(TKey key, TimeSpan? expiry = null, CancellationToken cancellationToken = default);
Task<bool> ExistsAsync(TKey key, CancellationToken cancellationToken = default);
Task PutAsync(TKey key, TValue value, TimeSpan? expiry = null, CancellationToken cancellationToken = default);
Task<bool> PutIfAbsentAsync(TKey key, TValue value, TimeSpan? expiry = null, CancellationToken cancellationToken = default);
Task<bool> EvictAsync(TKey key, CancellationToken cancellationToken = default);
Task<TValue?> ReloadAsync(TKey key, TimeSpan? expiry = null, CancellationToken cancellationToken = default);
// ---- 批量操作 ----
Task<Dictionary<TKey, TValue>> BatchGetAsync(IReadOnlyList<TKey> keys, CancellationToken cancellationToken = default);
Task<Dictionary<TKey, TValue>> BatchGetOrLoadAsync(IReadOnlyList<TKey> keys, TimeSpan? expiry = null, CancellationToken cancellationToken = default);
Task BatchPutAsync(IReadOnlyDictionary<TKey, TValue> data, TimeSpan? expiry = null, CancellationToken cancellationToken = default);
Task<long> BatchEvictAsync(IReadOnlyList<TKey> keys, CancellationToken cancellationToken = default);
}| 方法 | 行为 |
|---|---|
GetAsync |
纯缓存查询(L1 → L2,L2 命中回填 L1),不回源;未命中返回 default |
GetOrLoadAsync |
Cache-Aside 主入口:未命中时经 ILoader 回源并回填 L1/L2;回源为 null 且启用空值缓存时写入空值哨兵 |
ExistsAsync |
L1 或 L2 任一存在即 true |
PutAsync |
覆盖写:L2 SET + L1 回填 + 失效广播 |
PutIfAbsentAsync |
NX 语义,仅当 L2 不存在时写入,返回是否成功 |
EvictAsync |
移除 L1 + L2 并广播失效;返回 L2 是否确有删除 |
ReloadAsync |
强制回源并覆盖缓存(跳过缓存读取) |
BatchGetAsync |
L2 MGET 批量查询 + L1 回填,仅返回命中的非空值 |
BatchGetOrLoadAsync |
未命中 Key 经 ILoader.LoadManyAsync 批量回源;空值 Key 不重复回源 |
BatchPutAsync |
Pipeline 批量写入(支持值中含 null,走空值哨兵) |
BatchEvictAsync |
Pipeline 批量删除 + 失效广播,返回成功删除数 |
GetAsync与GetOrLoadAsync职责分离:只读兜底场景用前者(不会意外触发回源),典型读场景用后者。
回源加载器。命名空间:L2Cache.Abstractions.Policies。
public interface ILoader<TKey, TValue> where TKey : notnull
{
/// <summary>单条回源。返回 null 表示数据源无此数据。</summary>
Task<TValue?> LoadAsync(TKey key, CancellationToken cancellationToken = default);
/// <summary>批量回源。建议实现为一条 IN 查询;未命中的 Key 不放入返回字典。</summary>
Task<Dictionary<TKey, TValue>> LoadManyAsync(
IReadOnlyList<TKey> keys, CancellationToken cancellationToken = default);
}- 通过
WithLoader<TLoader>()(类型,从 DI 解析)或WithLoader(factory)(工厂)注册。 - Loader 生命周期与解析作用域由 DI 决定,可安全依赖 Scoped 服务(如
DbContext)。
命名空间:L2Cache(ServiceCollectionExtensions)。
public static IL2CacheBuilder AddL2Cache(
this IServiceCollection services, Action<L2CacheOptions> configure);
public interface IL2CacheBuilder
{
IServiceCollection Services { get; }
IL2CacheRegionBuilder<TKey, TValue> AddCache<TKey, TValue>(
string cacheName, Action<CacheRegionOptions>? configure = null) where TKey : notnull;
}
public interface IL2CacheRegionBuilder<TKey, TValue> where TKey : notnull
{
IServiceCollection Services { get; }
IL2CacheRegionBuilder<TKey, TValue> WithLoader<TLoader>() where TLoader : class, ILoader<TKey, TValue>;
IL2CacheRegionBuilder<TKey, TValue> WithLoader(Func<IServiceProvider, ILoader<TKey, TValue>> loaderFactory);
IL2CacheRegionBuilder<TKey, TValue> WithBackgroundRefresh(Action<BackgroundRefreshOptions>? configure = null);
}WithBackgroundRefresh() 启用后,后台服务会调度刷新 L1 中活跃的 Key:
public interface ICacheRefreshable<TKey> where TKey : notnull
{
Task RefreshKeyAsync(TKey key, CancellationToken cancellationToken = default);
}刷新优先读取 L2 最新值(避免回源风暴),L2 亦未命中时才回源。刷新间隔由 BackgroundRefreshOptions.Interval 或 ICacheRefreshPolicy 决定。
所有策略均可替换(services.Replace(...) 或传入 CacheDescriptor 对应实现):
| 接口 | 默认实现 | 职责 |
|---|---|---|
IKeyBuilder<TKey> |
DefaultKeyBuilder<TKey> |
业务 Key → 字符串缓存 Key(string/枚举/基元类型直接 ToString();复杂类型抛异常,要求自定义实现) |
IExpiryPolicy |
DefaultExpiryPolicy |
TTL 解析:显式 expiry → DefaultTtl → NullValue.Ttl;L1 = min(L2, MaxL1Ttl) |
ILockPolicy |
MemoryLockPolicy + DistributedLockPolicy(链式组合为 ChainedLockPolicy) |
回源合并与并发写控制 |
INullValuePolicy |
SentinelNullValuePolicy |
空值哨兵(@@NULL@@)的写入与识别 |
ICacheSerializer |
JsonCacheSerializer(Json 包) / MemoryPackCacheSerializer(MemoryPack 包) |
值 ↔ UTF-8 字节 |
ICacheInvalidationBus |
RedisPubSubInvalidationBus |
失效消息广播(可替换为 Kafka/RabbitMQ 等) |
ITelemetryProvider |
NoOpTelemetryProvider(核心包) / DefaultTelemetryProvider(Telemetry 包) |
Tracing/Metrics/日志埋点 |
- L2Cache.Serializers.Json:System.Text.Json,UTF-8,默认 CamelCase + 忽略 null + 中文友好编码。任意泛型类型均支持(反射元数据由 STJ 内部缓存)。
- L2Cache.Serializers.MemoryPack:高性能二进制序列化,适合大对象与极致吞吐场景。
自定义序列化器只需实现 ICacheSerializer 并注册替换:
services.Replace(ServiceDescriptor.Singleton<ICacheSerializer, MySerializer>());builder.Services.AddL2CacheTelemetry();ITelemetryProvider:StartActivity(Tracing)、RecordCacheHit/Miss/Write/...(Metrics 直方图与计数)、异常埋点。- 未安装 Telemetry 包时使用 NoOp 实现,全部调用为空操作且不产生分配。
RedisCacheStore 的所有操作均容忍连接故障:读取失败视为未命中、写入失败返回 false,异常仅记录日志。上层保持降级语义——Redis 不可用时自动退化为纯内存缓存,恢复后自动重新接管。