Sentinel指标持久化机制深度解析

MetricWriterSentinel 的指标(Metric)采集与持久化模块,核心目标是:

定时将运行时的实时监控数据(如 QPS、RT、线程数等)写入本地磁盘日志文件,供后续分析、展示或告警使用。

它通过两个关键类协同工作:

  • MetricTimerListener采集器,定期从内存中拉取指标
  • MetricWriter写入器,负责按规则将指标安全写入磁盘

下面从 设计意图、工作机制、文件策略、线程安全、扩展性 五个维度深入解析。


🎯 一、整体设计意图

Sentinel 需要记录每个资源(如 /api/order)在每一秒的:

  • 通过请求数(passQps)
  • 拦截请求数(blockQps)
  • 平均响应时间(rt)
  • 并发线程数(threadNum)

这些数据用于:

  • 控制台实时监控图表
  • 熔断/流控规则动态调整依据
  • 故障复盘与性能分析

不能一直存内存 (OOM),也不能每次请求都写磁盘(性能差),所以采用:

"内存聚合 + 定时批量落盘" 策略


🧩 二、核心组件协作流程

1. MetricTimerListener ------ 定时采集任务

java 复制代码
public void run() {
    Map<Long, List<MetricNode>> maps = new TreeMap<>(); // 按时间戳分组
    
    // 1. 遍历所有资源的 ClusterNode
    for (Entry<ResourceWrapper, ClusterNode> e : ClusterBuilderSlot.getClusterNodeMap().entrySet()) {
        ClusterNode node = e.getValue();
        Map<Long, MetricNode> metrics = node.metrics(); // 获取该资源每秒的指标
        aggregate(maps, metrics, node); // 合并到全局 maps
    }
    
    // 2. 加上全局 ENTRY_NODE(总入口流量)
    aggregate(maps, Constants.ENTRY_NODE.metrics(), Constants.ENTRY_NODE);
    
    // 3. 写入磁盘
    for (Entry<Long, List<MetricNode>> entry : maps.entrySet()) {
        metricWriter.write(entry.getKey(), entry.getValue());
    }
}

💡 ClusterNode.metrics() 返回的是 过去一分钟内每秒的快照(基于滑动窗口统计)


2. MetricWriter ------ 智能文件写入器

核心约束(见注释):
  1. 同 1 秒的数据必须写入同一个文件
  2. 单个文件大小有限制(防止单文件过大)
  3. 不同日期的数据必须分文件
  4. 文件命名规范{appName}-metrics.log.pid{pid}.yyyy-MM-dd.[number]
  5. 每个数据文件配一个索引文件.idx),记录"时间戳 → 文件偏移量"

📁 三、文件管理策略详解

🔹 文件命名示例

假设:

  • appName = order-service
  • pid = 12345
  • 当前时间 = 2025-12-08

则可能生成:

复制代码
order-service-metrics.log.pid12345.2025-12-08
order-service-metrics.log.pid12345.2025-12-08.1
order-service-metrics.log.pid12345.2025-12-08.2
...

.1, .2 表示当日第 2、3 个文件(因单文件超限而切分)

🔹 索引文件(.idx)作用

  • 每写入一批新时间戳的数据,就记录:

    java 复制代码
    outIndex.writeLong(time);   // 时间戳(秒)
    outIndex.writeLong(offset); // 在 .log 文件中的字节偏移位置
  • 用途:快速定位某秒数据在哪个文件的哪个位置,避免全文件扫描

🔹 自动清理旧文件

java 复制代码
private void removeMoreFiles() {
    List<String> list = listMetricFiles(baseDir, baseFileName);
    // 只保留 totalFileCount 个最新文件
    for (int i = 0; i < list.size() - totalFileCount + 1; i++) {
        delete old file and its .idx
    }
}
  • 默认保留 6 个文件(可配置)
  • 防止磁盘被日志撑爆

⚙️ 四、关键机制亮点

1. 按秒对齐写入

  • 所有 MetricNode 的时间戳被强制设为传入的 time(秒级)
  • 同一秒多次调用 write() 会追加写入同一文件段
  • 避免"同一秒数据分散在多个文件"

2. 跨天自动切文件

java 复制代码
if (isNewDay(lastSecond, second)) {
    closeAndNewFile(nextFileNameOfDay(time));
}
  • 利用 timeSecondBase(1970-01-01 00:00:00)计算"天数"
  • 确保 2025-12-08 23:59:592025-12-09 00:00:00 分属不同文件

3. 原子性 & 异常安全

  • write() 方法加 synchronized,保证多线程安全(虽然通常由单线程调度)
  • 捕获异常并 warn,防止任务崩溃导致指标丢失

4. 高效序列化

  • 使用 node.toFatString() 生成文本行(非 JSON,节省空间)

  • 示例格式(实际字段更多):

    复制代码
    1701234567000|order-service|/api/create|20|5|100|3

🔐 五、线程安全与性能

场景 说明
读取指标 ClusterNode.metrics() 内部使用 AtomicReferenceArray,无锁高性能
写入磁盘 MetricWriter.write()synchronized,但由单线程 ScheduledExecutorService 调用,无竞争
文件切换 closeAndNewFile() 原子完成旧文件关闭 + 新文件创建

✅ 整体设计 读无锁、写串行、异步落盘,兼顾性能与一致性


🌐 六、与 Sentinel 整体架构的关系

统计 pass/block/rt 每秒快照 定时拉取 调用 写入 写入 读取 读取 StatSlot ClusterNode MetricBucket MetricTimerListener MetricWriter metrics.log metrics.log.idx Sentinel Dashboard

  • 数据流:请求 → StatSlot → ClusterNode → MetricTimerListener → 磁盘
  • 控制流:Dashboard 可读取这些文件展示历史监控曲线

💡 一句话总结

MetricTimerListener 定时从内存统计结构中提取秒级指标,MetricWriter 按日期、大小、进程 ID 等规则智能地将这些指标写入带索引的日志文件,实现高性能、可回溯、低开销的运行时监控数据持久化。

这种设计使得 Sentinel 在 不依赖外部存储 的情况下,也能提供可靠的实时+历史监控能力,是其轻量级、嵌入式特性的典型体现。

相关推荐
组合缺一12 小时前
Solon AI 开发学习19 - 结合 Solon Flow 定制 ReAct 效果
java·人工智能·学习·ai·llm·openai·solon
菜鸟233号12 小时前
力扣404 左叶子之和
java·数据结构·算法·leetcode
零一科技12 小时前
瑞吉外卖项目,后端源码解析
java·spring boot·后端
浪潮IT馆12 小时前
win11安装maven
java·maven
g323086312 小时前
分布式框架seata AT模式源码分析
java·数据库·分布式
狂奔小菜鸡12 小时前
Day26 | Java集合框架概览
java·后端·java ee
♡喜欢做梦12 小时前
自动化测试入门(上)
java·css·selenium·测试工具·测试用例
海上彼尚12 小时前
React18+快速入门 - 3.组件通讯[完整版]
前端·javascript·react.js
赵得C12 小时前
软件设计师实战知识精粹:工程落地与架构设计考点解析
java·开发语言·设计模式