缓存治理的职责与取舍
使用与监测接入见 README。本文解释治理层的责任、旧实现替换和数据流。
依赖与读取流程
HybridCache 提供本地/共享读取、序列化和同实例并发加载合并。IGovernedCache 是应用侧的策略入口,GovernedCache 默认实现只约束键、策略、源加载预算和聚合监测,不再维护第二套缓存字典或逐键锁。
业务调用
→ 校验键、取消与绕过策略
→ cache.requests
→ HybridCache:本地读取 → 可选共享读取 → 同键加载合并
→ 未命中工厂:尝试领取预算 → cache.source.loads
→ 业务源加载 → 缓存回填
→ finally 释放预算、记录 cache.source.duration
预算耗尽只记录 cache.source.rejected,工厂没有开始,不记录加载次数或时长。工厂异常交回 HybridCache 和调用方,不作为不存在结果保存。已开始的失败/取消仍记录加载次数和时长,因此本组指标不是成功率指标。
关闭或绕过时直接调用加载器,跳过缓存预算与缓存指标。删除仍可清除先前值;绕过 Set 不回填。加载器接收组合取消令牌并负责依赖生命周期,不应捕获可能随首个请求释放的 DbContext。
键与策略
键在创建时对类型名称、Scope 和资源标识的长度前缀编码计算 SHA-256,再加入应用与 region。长度前缀防止分隔符拼接碰撞;类型参与身份防止不同 DTO 误用同一键。数据格式版本由 region 表达。摘要隐藏键原文,但不是授权或存储加密。
策略构造时生成不可变 HybridCacheEntryOptions。同一身份应采用一致策略,绝对过期避免热项无限续期,本地 TTL 不超过共享 TTL。正常值与 null 共用固定 TTL,保留一次原生读取;按结果动态设置负缓存 TTL 需要额外状态或另一缓存路径,本版不引入。
传递值类型 state 与静态委托,避免每次命中新增捕获对象。摘要、类型元数据和策略在各自适当位置预建或复用;原生缓存本身的分配仍受 DTO 类型、序列化和监听器影响,不能只凭静态委托宣称整个路径零分配。
监测为什么改用 Meter
旧 Lab 实现把缓存值、键元数据、操作列表和缓冲日志作为一条独立链路:CacheManager 调用 ICacheStore/IKeyStore,CacheMetadata 保存创建/访问/更新时间与读写计数,OperationLogBuffer 缓冲事件并定期刷新。它主要使用内存存储,不是持久审计库,但会额外维护逐键状态、操作对象和计时器。
现在将缓存正确性与监测职责分开。值的有效期、读取与加载合并由 HybridCache 维护;Meter 只发布数值,由 dotnet-counters 或 OpenTelemetry 监听。Counter/Histogram 不持有完整键、用户 Scope 或逐次操作记录,收集器承担聚合、导出和历史保留。已有 Observability 接入订阅该 Meter,Caching 包不反向依赖导出器。
| 旧能力 | 新实现 | 取舍 |
|---|---|---|
| 每键 Get/Set/Update 计数 | 区域级读取请求和源加载 Counter | 不等价;源加载不等于写入次数,也没有逐键统计 |
| 创建/最近访问/更新时间、值类型 | 无相应指标 | 不维护驻留元数据查询 API |
| 操作列表及成功/失败记录 | 原生缓存诊断、调用方错误处理 | 不提供历史回放或由这些 Counter 推导错误率 |
| 操作缓冲、定时刷新 | Meter → 外部收集器/导出器 | 不再运行缓存专用日志刷新计时器;导出器可有自己的周期任务 |
| 完整键定位 | 指标只使用固定 region | 控制序列基数和标识泄露;需要定位时由宿主设计受控日志 |
这次替换保留了聚合容量观察,主动放弃逐键监测模型。缓存容量、当前驻留数量、逐键操作审计与精确层级命中率没有被 Meter 自动补齐;后续若出现这些需求,应分别选择原生后端统计、受控诊断接口或独立审计方案,不把无界操作列表重新放入读取路径。
Meter 不自己保存历史累计值。未订阅时,后来启动的监听器不能回放过去操作;观察窗口、聚合和持久保存由监听器决定。Histogram 的时间单位为 ms,统计的是源工厂,不能把其 P95 当作缓存读取或 HTTP 请求 P95。
标签与性能
指标仅按固定 region 分类,不使用完整键、Scope、资源 ID 或用户输入作标签。区域数量仍由宿主管理。请求 Counter 位于启用读取路径;源计数和时长仅在真正执行工厂时记录。无监听器与有监听器都需要分别测量,导出器的聚合、序列化与网络 I/O 还应与治理层成本分开报告。
性能记录分别报告原生/治理读取、键构建和监听开销;没有把“预建键命中零分配”扩大为所有业务访问零分配。
事务、失效与故障
数据库提交与缓存失效没有共同事务。提交前删除可能让旧数据重新填充,因此业务提交后失效。若必须保证通知最终产生,可在同一业务事务写 Outbox,再异步处理失效;发送确认不代表所有实例已清理本地副本。
删除不能阻止已启动的旧加载回填,共享后端删除也不使其他实例 L1 立即失效。要求严格新鲜的数据绕过缓存;更严格的版本协议和全实例广播需要独立用例验证,竞争消费的业务队列不完成广播。
后端故障行为沿用 HybridCache,源预算限制同时回源数量,不是全局请求限流。宿主负责总缓存容量、后端连接、提交结果不确定时的处置、可靠失效、访问权限和保留期。