多级缓存一致性矩阵:ABP vNext 下的旁路 / 写穿 / 写回组合实战

多级缓存一致性矩阵:ABP vNext 下的旁路 / 写穿 / 写回组合实战



1. 概念速览与边界

  • 多级缓存:L1 进程内 IMemoryCache 🔐;L2 分布式 IDistributedCache/Redis 🧑‍💻;L3 边缘(CDN/反代) 🌐。
  • 写策略:旁路(Cache-Aside) 🔄、写穿(Write-Through) 📝、写回/写后(Write-Back/Behind) 🔄。
  • 一致性指标:读你所写(强一致性/同步) ⚖️、软实时(≤N 秒追平) ⏱️、最终一致 🔁。
  • 本文关注策略组合与治理,讨论如何在多租户 SaaS 环境下,合理配置缓存,提升系统的可用性与性能 ⚡。

2. 体系结构

在这里插入图片描述

  • 读路径:L1 命中 → 否则查 L2 → 再否回源并回写 L2/L1(带 SWR 与抖动)。SWR=先返回陈旧值,后台重验证
  • 写路径:根据业务在旁路/写穿/写回之间选择;多实例间靠 Redis Pub/Sub 做 L1 失效广播。
  • 多租户:ABP 支持多租户隔离,建议采用 租户隔离的 Key 空间与 TTL 策略,保证不同租户缓存不受影响。

3. Key 规范与租户维度 TTL

Key 模板{app}:{env}:{tenant}:{entity}:{id}(ABP 统一管理缓存前缀)
TTL 计算TTL = BaseTTL(tenant,entity) × Jitter(±10~25%)(加 TTL 抖动 以减少雪崩效应)。

策略提供器接口(放入 ABP 模块中)

public interface ICachePolicyProvider {
   
   
    CachePolicy Get(string tenant, string entity);  // 包含 hardTtl, softTtl, jitter, negativeTtl 等
}
public record CachePolicy(TimeSpan HardTtl, TimeSpan SoftTtl, double JitterPct,
                          TimeSpan NegativeTtl, bool EnableSWR, bool EnableBloom);

4. 三种写法的组合与适用

写法 一致性 典型风险 适用场景
旁路 Cache-Aside 软实时/最终一致 击穿/回源洪峰 读多写少,允许轻微延迟
写穿 Write-Through 较强一致性 写延迟变大 单体/中低写压、需强一致性
写回 Write-Back/Behind 低延迟/高吞吐 数据丢失/回放复杂 高写入、可容忍短暂不一致

组合建议

  • 读多写少:旁路 + SWR + Single-Flight + 负缓存
  • 中等写压、需读你所写:写穿 + L1 失效广播
  • 写峰值极高:写回(Redis Streams/队列 + 持久化日志)+ 回放与审计

5. 雪崩 / 穿透 / 击穿 治理

  • 雪崩:通过 TTL 抖动、SWR 软过期、热点预热、分布式锁保护回源。
  • 穿透RedisBloom(布隆过滤器)+ 负缓存(短 TTL)+ 参数白名单。
  • 击穿Single-Flight(同 key 回源去重)+ 后台刷新。

Single-Flight(L2 保护示例)

public sealed class SingleFlight {
   
   
    private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new();
    public async Task<T> RunAsync<T>(string key, Func<Task<T>> loader) {
   
   
        var gate = _locks.GetOrAdd(key, _ => new SemaphoreSlim(1, 1));
        await gate.WaitAsync();
        try {
   
   
            return await loader();
        } finally {
   
   
            gate.Release();
            _locks.TryRemove(key, out _);
        }
    }
}

6. SWR:Soft-Expire

  • 逻辑:当 SoftTTL 超过但 HardTTL 未到时,返回陈旧值并后台刷新数据。
  • 优点:有效削峰、改善尾延迟;缺点:短时间内读到陈旧数据。

SWR 实现骨架(Redis + 单飞 + 失效广播)

public async Task<T?> GetWithSwrAsync<T>(
    string key, Func<Task<T>> dbLoader, CachePolicy p,
    IDistributedCache cache, SingleFlight sf, ISubscriber pub, string? invChannel = null)
{
   
   
    var bytes = await cache.GetAsync(key);
    var entry = bytes?.FromBytes<CacheEnvelope<T>>();

    // 新鲜
    if (entry is {
   
    } && entry.SoftExpireAt > DateTimeOffset.UtcNow)
        return entry.Value;

    // 软过期:先旧后新
    if (entry is {
   
    } && entry.HardExpireAt > DateTimeOffset.UtcNow) {
   
   
        _ = Task.Run(async () => {
   
   
            var v = await sf.RunAsync(key, dbLoader, CancellationToken.None);
            await cache.SetAsync(key, new CacheEnvelope<T>(v, p).ToBytes(), new DistributedCacheEntryOptions {
   
   
                AbsoluteExpirationRelativeToNow = p.HardTtl.WithJitter(p.JitterPct)
            });
            if (invChannel is not null) await pub.PublishAsync(invChannel, key);
        });
        return entry.Value;
    }

    // 完全过期:受单飞保护
    var fresh = await sf.RunAsync(key, dbLoader);
    await cache.SetAsync(key, new CacheEnvelope<T>(fresh, p).ToBytes(),
        new DistributedCacheEntryOptions {
   
    AbsoluteExpirationRelativeToNow = p.HardTtl.WithJitter(p.JitterPct) });
    return fresh;
}

public readonly record struct CacheEnvelope<T>(T Value, DateTimeOffset SoftExpireAt, DateTimeOffset HardExpireAt) {
   
   
    public CacheEnvelope(T value, CachePolicy p) : this(
        value,
        DateTimeOffset.UtcNow + p.SoftTtl
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kookoos

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值