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 逐出策略触发时机
触发时机不是达到上限才执行,而是基于驱动模型分三级递进:
- 软触发:水位达到上限的 80%,提前标记“可逐出条目”,但不立即移除。
- 硬触发:水位达到上限的 95% 或插入操作本身会导致超限,同步执行逐出。
- 紧急触发:水位达到上限的 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 落地步骤
- 基准测试:在压测环境录制缓存插入量与实际内存的映射曲线。
- 设定上限:根据压测结果选取安全系数(建议上限 ≤ 堆的25%)并写入配置中心。
- 配置水位线:在监控系统(Prometheus + Grafana)中创建3条水位线告警规则。
- 灰度验证:先部署 10% 的实例,观察24小时逐出曲线和GC频率。
- 全量生效:确认无异常后,全量推送配置,并持续观察7天。
4.9 验收标准
- 在峰值每秒 10,000 写入时,缓存内存不超过上限的 98%。
- 逐出操作响应时间 < 5ms(同步模式)或 < 1ms(异步模式)。
- 水位线告警准确率 > 99%,无漏报、误报。
- 缓存容量归零重置后,重新加载时间不超过 30 秒。