4. 本地缓存容量规划:固定内存上限、逐出策略触发时机、监控水位线设计

4.1 架构边界与分层职责

在分布式系统中,本地缓存位于应用进程内部,是距离业务代码最近的一层存储。容量规划的核心边界在于:本地缓存不负责持久化,它的生命周期严格受限于进程内存。因此,架构上必须通过三层隔离来防止缓存失控:

  • 应用层:指定缓存的数据类型、最大条目数或最大内存值。
  • 缓存中间件层(如 Caffeine、Guava、Ehcache):执行内存预算、逐出策略、统计指标。
  • JVM/OS 层:提供实际物理内存,并受 -Xmx 和堆外限制。

4.2 组件职责表

组件 职责 风险点
缓存策略管理器 统一管理容量上限、逐出触发阈值、水位线回调 单例生命周期与 Application 不对齐
容量计数器 实时累积 key-value 字节数(含对象头开销) 序列化后实际内存与原对象差异
逐出调度器 在达到阈值后异步执行逐出任务 异步延迟导致瞬时内存超限
水位线监控器 定期采集当前用量、触发告警或降级 采集周期过长导致漏检

4.3 固定内存上限设计

容量规划的第一步是定义“内存桶”的大小。业界推荐按堆的 10%~30% 分配本地缓存,但必须预留堆外序列化缓冲区。

  • 堆内缓存上限:-Xmx2g 时,缓存上限设为 512MB ~ 768MB。
  • 估算公式:
    实际内存 = 键平均大小 * 条目数 + 值平均大小 * 条目数 + 对象头(≈48B) * 2 * 条目数
  • 配置示例(Caffeine):
Caffeine.newBuilder()
    .maximumWeight(512_000_000)    // 512 MB
    .weigher((key, value) -> key.toString().length() + value.toString().length())
    .build()

4.4 逐出策略触发时机

触发时机不是达到上限才执行,而是基于驱动模型分三级递进:

  1. 软触发:水位达到上限的 80%,提前标记“可逐出条目”,但不立即移除。
  2. 硬触发:水位达到上限的 95% 或插入操作本身会导致超限,同步执行逐出。
  3. 紧急触发:水位达到上限的 100% 或溢出,强制清空低优先级缓存。
最佳实践:将逐出策略配置为异步 + 合并请求,避免每次插入都触发昂贵的排序逐出。Caffeine 默认使用 Window TinyLFU 策略,只在写操作后异步维护频率 sketch,性能较好。

4.5 监控水位线设计

水位线是一组预配置的百分比阈值,用于驱动告警与自动伸缩。推荐采用三级报警体系:

水位线名称 阈值(占上限百分比) 控制流动作
低水位 60% 记录日志、增加采样频率
中水位 85% 触发逐出预检、发出告警
高水位 95% 立即逐出、拒绝非关键写入(降级)
# 监控配置示例(Prometheus + Micrometer)
cache.usage.percent{name="user_session",} 0.78
cache.eviction.count{name="user_session",} 124
cache.watermark.level{name="user_session",} "medium"

4.6 数据流与控制流

整体流程如下(仅含与容量规划相关的部分):

本地缓存容量规划数据流与控制流 应用请求读写缓存 容量计数器(累加) 水位判断 低水位:记录日志 中水位:逐出预检 高水位:立即逐出 逐出调度器(异步/同步) 监控水位线告警

4.7 选型权衡:固定容量 vs 动态容量

维度 固定内存上限 动态/自适应容量
内存确定性 高,始终不超预算 低,可能突发超限
实现复杂度 低(配置即用) 高(需自研自适应算法)
冷启动表现 容量恒定,无需调整 需要持续学习和调整
适用场景 大多数生产系统 流量波动极大、对内存不敏感的场景
风险提醒:固定内存上限若设置过高(>堆的40%),极易引发 Full GC 或 OOM。一直建议配合 jstat 或 Arthas 验证真实内存占用,而非仅依赖预估公式。

4.8 落地步骤

  1. 基准测试:在压测环境录制缓存插入量与实际内存的映射曲线。
  2. 设定上限:根据压测结果选取安全系数(建议上限 ≤ 堆的25%)并写入配置中心。
  3. 配置水位线:在监控系统(Prometheus + Grafana)中创建3条水位线告警规则。
  4. 灰度验证:先部署 10% 的实例,观察24小时逐出曲线和GC频率。
  5. 全量生效:确认无异常后,全量推送配置,并持续观察7天。

4.9 验收标准

  • 在峰值每秒 10,000 写入时,缓存内存不超过上限的 98%。
  • 逐出操作响应时间 < 5ms(同步模式)或 < 1ms(异步模式)。
  • 水位线告警准确率 > 99%,无漏报、误报。
  • 缓存容量归零重置后,重新加载时间不超过 30 秒。