4. 台账体系:误差预算台账的数据结构与存储设计

误差预算台账是代理缓存空间回收策略的“记账本”。它记录每个制品仓库、每个缓存分片在单位时间内的预算消耗、实际使用量以及剩余额度。台账的数据结构必须支持高频写入(每次缓存命中/未命中均需更新)、快速查询(回收决策时需在毫秒级获取当前预算状态)以及定期归档(历史数据用于趋势分析)。

4.1 台账核心数据模型

台账采用分层聚合模型,从最细粒度的“缓存条目”到“仓库分片”,再到“仓库整体”,逐层汇总预算数据。核心实体包括:

  • 预算账户(BudgetAccount):每个仓库或分片的预算总额、消耗速率、剩余额度。
  • 预算流水(BudgetLedger):单次缓存操作(命中/未命中/淘汰)的明细记录。
  • 预算快照(BudgetSnapshot):按固定时间窗口(如5分钟)生成的聚合摘要。
实体 主键 关键字段 写入频率 保留周期
预算账户 repo_id + shard_id total_budget, used_budget, remaining, rate_limit 低(配置变更时) 永久
预算流水 ledger_id (UUID) repo_id, shard_id, operation_type, cost, timestamp 极高(每次缓存操作) 7天
预算快照 repo_id + window_start total_ops, hits, misses, evictions, budget_consumed 中(每5分钟) 90天
设计要点:预算流水采用写优化存储引擎(如LSM-Tree),避免高频写入导致台账成为性能瓶颈。预算快照则使用列式存储,便于后续做时间序列分析。

4.2 误差预算计算公式

误差预算的核心逻辑围绕“预算消耗速率”与“预算恢复速率”展开。每个时间窗口结束时,系统根据实际缓存命中率与目标命中率的偏差,计算预算消耗量。

// 误差预算消耗计算(伪代码)
function calculateBudgetConsumption(repoId, windowDuration) {
    let stats = getWindowStats(repoId, windowDuration);
    let targetHitRate = getTargetHitRate(repoId);
    let actualHitRate = stats.hits / (stats.hits + stats.misses);
    let deviation = targetHitRate - actualHitRate;

    // 偏差越大,消耗的预算越多
    let consumption = 0;
    if (deviation > 0) {
        // 命中率低于目标,消耗预算
        consumption = deviation * stats.totalRequests * budgetUnitCost;
    } else {
        // 命中率高于目标,恢复预算(但不能超过上限)
        consumption = -Math.abs(deviation) * stats.totalRequests * budgetUnitCost * 0.5;
    }

    return clamp(consumption, -maxRecoveryPerWindow, maxConsumptionPerWindow);
}
关键指标:
  • 预算消耗速率:单位时间内因缓存未命中而消耗的预算点数。
  • 预算恢复速率:缓存命中率超过目标时,预算缓慢恢复的速度(通常为消耗速率的50%)。
  • 预算赤字阈值:当剩余预算低于该阈值时,触发激进回收策略。

4.3 存储架构设计

台账数据采用混合存储架构,兼顾写入性能与查询灵活性:

  • 热数据层(内存):预算账户和最近5分钟的流水缓存在Redis或类似内存数据库中,支持微秒级读写。
  • 温数据层(本地SSD):预算流水按小时分片写入本地RocksDB实例,保留最近7天数据。
  • 冷数据层(对象存储):预算快照和超过7天的流水归档到S3/MinIO,用于长期趋势分析。
误差预算台账混合存储架构 热数据层(内存) 预算账户(Redis Hash) 最近5分钟流水(Redis Stream) 响应时间:< 1ms 温数据层(本地SSD) 预算流水(RocksDB,按小时分片) 预算快照(RocksDB,按窗口聚合) 保留周期:7天 | 响应时间:< 10ms 冷数据层(对象存储) 历史流水归档(Parquet格式) 历史快照(Parquet格式) 保留周期:90天+ | 响应时间:< 500ms 数据从热层向冷层异步迁移,迁移策略基于时间阈值和容量水位

4.4 台账查询接口

回收策略引擎通过以下核心接口访问台账数据:

接口名称 输入参数 返回数据 响应时间要求
getBudgetStatus repo_id, shard_id 剩余预算、消耗速率、赤字标志 < 5ms
getWindowStats repo_id, window_start, window_end 命中率、未命中数、预算消耗量 < 20ms
getTopConsumers limit, time_range 预算消耗最高的仓库列表 < 100ms
recordOperation repo_id, shard_id, op_type, cost 写入确认 < 2ms(异步)
注意:recordOperation 接口必须支持批量提交和异步写入,避免每次缓存操作都同步写台账导致性能下降。建议使用消息队列(如Kafka)缓冲流水数据,再由消费者批量写入温数据层。

4.5 台账一致性保障

在分布式缓存场景下,台账数据的一致性面临挑战。采用以下策略保障:

  • 最终一致性:预算流水允许短暂延迟(秒级),但预算账户的剩余额度必须保证最终正确。
  • 乐观锁:更新预算账户时使用CAS(Compare-And-Swap)操作,避免并发写入导致预算超支。
  • 补偿机制:每5分钟运行一次对账任务,将流水实际消耗与账户余额进行比对,发现偏差时自动修正。
台账一致性保障流程 缓存操作触发 写入流水队列 异步批量写入 定时对账任务 修正预算偏差 对账任务每5分钟执行一次,扫描流水与账户余额的差值,差值超过阈值时触发自动修正 修正操作记录到审计日志,用于事后追溯

4.6 台账数据生命周期管理

为避免台账数据无限增长,制定以下生命周期策略:

数据类别 热层保留 温层保留 冷层保留 最终处置
预算账户 永久 — — 不删除
预算流水 5分钟 7天 90天 删除
预算快照 1小时 7天 90天 聚合后删除
审计日志 — 30天 1年 归档至冷存储
最佳实践:预算快照在冷层保留90天后,可按月聚合生成月度摘要,原始快照删除。月度摘要保留3年,用于年度容量规划分析。