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,用于长期趋势分析。
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分钟运行一次对账任务,将流水实际消耗与账户余额进行比对,发现偏差时自动修正。
4.6 台账数据生命周期管理
为避免台账数据无限增长,制定以下生命周期策略:
| 数据类别 | 热层保留 | 温层保留 | 冷层保留 | 最终处置 |
|---|---|---|---|---|
| 预算账户 | 永久 | — | — | 不删除 |
| 预算流水 | 5分钟 | 7天 | 90天 | 删除 |
| 预算快照 | 1小时 | 7天 | 90天 | 聚合后删除 |
| 审计日志 | — | 30天 | 1年 | 归档至冷存储 |
最佳实践:预算快照在冷层保留90天后,可按月聚合生成月度摘要,原始快照删除。月度摘要保留3年,用于年度容量规划分析。