深刻剖析分布式监控 CAT —— 消息文件存储

项目简介

CAT(Central Application Tracking),是基于 Java 开发的分布式实时监控系统。CAT 目前在美团点评的产品定位是应用层的统一监控组件,在中间件(RPC、数据库、缓存、MQ 等)框架中获得普遍应用,为各业务线提供系统的性能指标、健康情况、实时告警等。java

CAT 目前在美团点评已经基本覆盖所有业务线,天天处理的消息总量 3200 亿+,存储消息量近 400TB,在通讯、计算、存储方面都遇到了很大的挑战。git

感兴趣的朋友欢迎 Star 开源项目 github.com/dianping/ca…github

消息模型

组织关系

消息模型UML图

消息类型

消息类型 职责 适用场景
Transaction 记录一段代码的执行时间和次数 1. 执行时间较长的业务逻辑监控。
2. 记录完整调用过程。
Event 记录一段代码的执行次数或事件是否发生 统计计数或异常事件记录
Metric 记录一个业务指标的变化趋势 业务指标的发生次数、平均值、总和,例如商品订单。
Heartbeat 按期上报数据或执行某些任务 按期上报统计信息,如 CPU 利用率、内存利用率、链接池状态等。

埋点示例

public void shopInfo() {
    Transaction t1 = Cat.newTransaction("URL", "/api/v1/shop");

    try {
        Transaction t2 = Cat.newTransaction("Redis", "getShop");
        String result = getCache();
        t2.complete();

        if (result != null) {
            Cat.logEvent("CacheHit", "Success");
        } else {
            Cat.logEvent("CacheHit", "Fail");
        }

        Transaction t3 = Cat.newTransaction("Rpc", "Call");
        try {
            doRpcCall();
        } catch (Exception e) {
            t3.setStatus(e);
            Cat.logError(e);
        } finally {
            t3.complete();
        }
    } catch (Exception e) {
        t1.setStatus(e);
        Cat.logError(e);
    } finally {
        t1.complete();
    }
}

private String getCache() throws InterruptedException {
    Thread.sleep(10); // mock cache duration
    return null;
}

private void doRpcCall() {
    throw new RuntimeException("rpc call timeout"); // mock rpc timeout
}
复制代码

LogView 消息树

LogView 不只能够分析核心流程的性能耗时,并且能够帮助用户快速排查和定位问题。例如上述埋点示例对应的 LogView:算法

  • Transation 消息是可嵌套的。
  • logError 可记录异常堆栈,是一种特殊的 Event 消息。

logview示例

分布式调用链路

分布式logview示例

CAT 能够提供简单的分布式链路功能,典型的场景就是 RPC 调用。例如客户端 A 调用服务端 B,客户端 A 会生成 2 个 MessageID:表示客户端 A 调用过程的 MessageID-1 和表示服务端 B 执行过程的 MessageID-2,MessageID-2 在客户端 A 发起调用的时候传递给服务端 B,MessageID-2 是 MessageID-1 的儿子节点。数据库

消息流水线

消息流水线

如上图所示,实时报表分析是整个监控系统的核心,CAT 服务端接收客户端上报的原始数据,分发到不一样类型的 Analyzer 线程中,每种类型的任务由一组 Analyzer 线程构成。因为原始消息的数量庞大,因此须要对数据进行加工、统计后生成丰富的报表,知足业务方排查问题以及性能分析的需求。api

其中 Logview 的 Analyzer 线程是本文讨论的重点,它会收集全量的原始消息,并实时写入磁盘,相似实现一个高吞吐量的简易版消息系统。此外须要具有必定限度的随机读能力,方便业务方定位问题发生时的“案发现场”缓存

对于历史的 Logview 文件会异步上传至 HDFS。性能优化

消息文件存储

CAT 针对消息写多读少的场景,设计并实现了一套文件存储。以小时为单位进行集中式存储,每一个小时对应一个存储目录,存储文件分为索引文件和数据文件。用户能够根据 MessageID 快读定位到某一个消息。app

消息 ID 设计

CAT 客户端会为每一个消息树都会分配惟一的 MessageID,MessageID 总共分为四段,示例格式:shop.service-0a010101-431699-1000。框架

  • 第一段是应用名shop.service。
  • 第二段是客户端机器 IP 的16进制码,0a010101 表示10.1.1.1。
  • 第三段是系统当前时间除以小时获得的整点数,431699 表明 2019-04-01 19:00:00。
  • 第四段是客户端机器当前小时消息的连续递增号。(存储设计的重要依据点

文件存储 V1.0

整体概貌

V1.0 版本的文件存储设计比较简单粗暴,每一个客户端 IP 节点对应分别对应一个索引文件和数据文件。

消息存储V1.0整体概貌

单个 IP 视角

单个IP视角

  • 每一个索引内容由存储块首地址和块内偏移地址组成,共 6byte。
  • 每一个索引内容的序号与消息序列号一一对应,由于消息序列号是连续递增号,因此索引文件基本能够保证是顺序写。
  • 为了减小磁盘IO交互和写入时间,消息采用批量 Gzip 压缩后顺序 append 至数据文件。

优缺点分析

优势 缺点
1. 简单易懂,实现复杂度不高。
2. 根据消息序列号可快速定位索引。
1. 随着业务规模不断扩展,存储文件的数量并不可控。
2. 数据文件节点过多致使随机 IO 恶化,机器 Load 飙高。

文件存储 V2.0

V2.0 文件存储进行了从新设计,以解决 V2.0 数据文件节点过多以及随机 IO 恶化的问题。

整体概貌

消息存储V2.0整体概貌

V2.0 核心设计思想:

  • 合并同一个应用的全部 IP 节点。
  • 引入多级索引,创建 IP、Index、DataOffset 的映射关系。
  • 同一个 IP 的索引数据尽量保证顺序存储。

单个索引文件视角

单个索引文件视角

索引文件存储的特色:

  • 须要根据 IP + Index 创建一级索引。
  • 不一样 IP 节点跳跃式存储,每次划分一段连续且固定大小的存储空间。
  • 同一个 IP 节点根据 Index 在每块固定大小的存储空间内顺序存储。

最小索引单元视角

最小索引单元视角

上图是索引结构的最小单元,每一个索引文件由若干个最小单元组成。每一个单元分为 4 * 1024 个 Segment,第一个 Segment 做为咱们的一级索引 Header,存储 IP、消息序列号与 Segment 的映射信息。剩余 4 * 1024 - 1 个 Segment 做为二级索引,存储消息的地址。一级索引和二级索引都采用 8byte 存储每一个索引数据。

一级索引 Header

  • 一级索引共由 4096 个 8byte 构成。

  • 每一个索引数据由 64 位存储,前 32 位为 IP,后 32 位为 baseIndex。

    baseIndex = index / 4096,index 为消息递增序列号。

二级索引

  • 二级索引共由 4095 个 segment 构成,每一个 segment 由 4096 个 8byte 构成。
  • 每一个索引数据由 64 位存储,前 40 位为存储块的首地址,后 24 位为解压后的块内偏移地址。

一级索引 Header 与二级索引关系

  • 一级索引第一个 8byte 存储可存储魔数(图中用 -1 表示),用于标识文件有效性。
  • 一级索引剩余 4095 个 8byte 分别与二级索引中每一个 segment 顺序一一对应。

如何定位一个消息

  1. 根据应用名定位对应的索引文件和数据文件。

  2. 加载索引文件中的全部一级索引,创建 IP、baseIndex、segmentIndex 的映射表。

    从整个索引文件角度看,segmentIndex 是递增的,1 ~ 409五、4097 ~ 8291,以此类推。

  3. 根据消息序列号 index 计算得出 baseIndex。

  4. 经过 IP、baseIndex 查找映射表,定位 segmentIndex。

  5. 计算消息所对应segment的偏移地址:segmentOffset = (index % 4096) * 8,得到索引数据。

  6. 根据索引数据中块偏移地址读取压缩的数据块,Snappy 解压后根据块内偏移地址读取消息的二进制数据。

总结

针对相似消息系统的数据存储,索引设计是比较重要的一环,方案并非惟一的,须要不断推敲和完善。文件存储经常使用的一些性能优化手段:

  • 批量、顺序写,减小磁盘交互次数。
  • 4K 对齐写入。
  • 数据压缩,经常使用的压缩算法有 Gzip、Snappy、LZ4。
  • 对象池,避免内存频繁分配。

实践出真知,推荐你们学习下 Kafka 以及 RocketMQ 源码,例如 RocketMQ 中单个文件混合存储的方式、相似 HashMap 结构的 Index 文件设计以及内存映射等都是比较好的学习资源。

转载请注明出处,欢迎关注个人公众号:亚普的技术轮子

亚普的技术轮子
相关文章
相关标签/搜索