架构解析系列-Dubbo Metrics 基本原理及扩展 OTLP 协议

本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!
关于 dubbo metrics 的使用可以参考dubbo-metrics可观测性 Metrics Proposal 两篇文档。

dubbo 的 metrics 目前是基于 micrometer 实现,这里关于整体的代码结构与工作流程官方文档中阐述的比较清楚,本篇文档主要是补充官方文档之外的更多代码实现细节上的内容,以及简要阐述如何将 metrics 通过 OTLP 协议对外暴露的实现思路。

理解 MetricsEvent、MetricsListener、MetricsCollector 和 MetricsDispatcher

dubbo metrics 数据收集主要入口是 provider 端的 MetricsClusterFilter 和 consumer 端的 MetricsFilter 两个 Filter 扩展。metrics 数据的流转是通过 个事件总线进行的异步化处理,以降低埋点这种非核心代码的耦合度,从而有效的降低指标埋点所带来的性能消耗。

MetricsEvent

MetricsEvent 抽象类,在 dubbo 的度量系统中主要用于封装和处理度量事件;TimeCounterEvent 在 dubbo 的度量系统中用于标记某些类型的事件,并允许自动记录开始和结束时间,提供时间对 TimePair,它提供了一种灵活的方式来处理和记录度量事件的时间信息。下图是 MetricsEvent 的类图结构:

  • MetricsEvent:
    • 存储度量事件的源对象,即 ApplicationModel 实例。这个实例包含了应用的所有信息,如服务、模块、实例等。
    • 提供了一种机制来附加额外的信息到度量事件中。这些信息可以通过 attachments 字段来存储和获取
    • 提供了一个 MetricsDispatcher 实例,用于分发度量事件。
    • 提供了一种方式来检查度量事件的可用性。如果度量事件不可用,那么它将不会被分发
    • 提供了一个 TypeWrapper 实例,用于确定度量事件的类型。
    • 提供了一种方式来自定义度量事件在被发布后的行为,这可以通过覆盖 customAfterPost 方法来实现。
  • TimeCounterEvent :
    • 继承了 MetricsEvent 类的所有功能,如存储度量事件的源对象,附加额外的信息到度量事件中,分发度量事件等。
    • 提供了一个 TimePair 实例,用于记录事件的开始和结束时间。这个实例在类的构造函数中通过 TimePair.start() 方法创建
    • 提供了一个 getTimePair 方法,用于获取记录的时间对

其他的诸如 MetadataEvent 等均属于某特定场景的子类事件,读者可以自行查看。

MetricsListener

MetricsListener 定义了一个度量事件监听器应该具备的基本行为,其内部提供一个 onEvent 方法,用于度量事件发生时被调用。MetricsLifeListener 在 MetricsListener 基础上扩展了事件的两个生命周期方法 :onEventFinish 和 onEventError,这两个方法分别在一个度量事件完成和出错时被调用。

MetricsListener 子类扩展出了两个部分,一块是 xxxListener ,一块是 xxxCollector。可以参考下面两张图所示:

  • MetricsListener
  • MetricsCollector

从类图结构和代码分析来看,指标数据的采集最终会由 MetricsCollector 来完成,MetricsCollector 依托 MetricsListener 监听 dubbo 服务的调用事件,收集各种指标数据。实际上在阅读代码的过程中发现,当 MetricsEventBus 接收到发布的信息时,首先是将信息转发到所有 MetricsCollector 中,如下图所示:

对于 CombMetricsCollector 的实现(上面 4 种均是),它们又会调用自己创建的 MetricsEventMulticaster 再次转发消息,到具体指标的监听器,然后这些监听器就会根据自己的逻辑修改 Collector 中的指标计数。

MetricsDispatcher

MetricsDispatcher 本质上就是一个简单的度量事件发布器,它实现了 MetricsEventMulticaster 接口。这个类的主要职责是管理度量事件的监听器,并在度量事件发生时通知这些监听器。其父类 SimpleMetricsEventMulticaster 中维护了一个 listeners 列表用于存储所有的度量事件监听器,这套设计应该是有参考 spring 的事件机制实现的。

在 MetricsDispatcher 的构造方法中,会通过 dubbo 自身的 SPI 机制将框架默认的一些 MetricsCollector 添加到 listeners 列表中。

实际上,这里的 MetricsDispatcher 并不是单纯只有一个 MetricsDispatcher,而是一组。

它们均继承自 SimpleMetricsEventMulticaster,因此它们都具有注册监听、转发事件的能力;每个 SubDispatcher 会绑定一个 Collector ,以 DefaultSubDispatcher 为例

DefaultSubDispatcher主要负责注册核心RPC调用次数指标,包括:

  • 请求次数 (METRIC_REQUESTS)
  • 请求成功次数(METRIC_REQUESTS_SUCCEED)
  • 请求失败次数(METRIC_REQUEST_BUSINESS_FAILED)

这三个指标在内部实现上会映射成三个 MetricsCat,MetricsCat 是一个封装类,它包含了 MetricsKey 的行为,保存了键的完整内容(MetricsPlaceValue),对应的收集器(CombMetricsCollector),以及键级别的事件监听器(AbstractMetricsKeyListener),它将这些组合在一起,提供了一种灵活的方式来处理度量事件,借用官方文档中的描述就是:为特定指标生产指标监听器的工厂

理解 MetricsReporter

这部分主要是指标上报相关的核心设计。MetricsReporter 是将 Metrics 信息暴露给外部系统,dubbo 中默认提供了两种指标报告器 DefaultMetricsReporter 和 PrometheusMetricsReporter,如下图所示:

每个 MetricsReporter 都对应一个 MetricsReporterFactory,这里就是简单工厂模式的实现。

下面分析下 MetricsReporter 初始化的逻辑。

initMetricsReporter 初始化逻辑解析

在应用的初始化和启动过程中(具体代码参考:org.apache.dubbo.config.deploy.DefaultApplicationDeployer),通过 initMetricsReporter 方法来完成,下面摘取和 MetricsReporter 相关的部分代码:

java 复制代码
// 通过 SPI 机制获取 MetricsReporterFactory
MetricsReporterFactory metricsReporterFactory =
                getExtensionLoader(MetricsReporterFactory.class).getAdaptiveExtension();
MetricsReporter metricsReporter = null;
try {
  	// 通过 MetricsReporterFactory 创建 MetricsReporter
    metricsReporter = metricsReporterFactory.createMetricsReporter(metricsConfig.toUrl());
} catch (IllegalStateException e) {
    // 省略
}
// MetricsReporter 初始化
metricsReporter.init();
applicationModel.getBeanFactory().registerBean(metricsReporter);
// 如果当前不是使用默认协议,则也将默认的 MetricsReporter 创建出来
// 实际上目前仅提供了 default 和 prometheus 两种协议实现方式
if (!PROTOCOL_DEFAULT.equals(metricsConfig.getProtocol())) {
    DefaultMetricsReporterFactory defaultMetricsReporterFactory =
            new DefaultMetricsReporterFactory(applicationModel);
    MetricsReporter defaultMetricsReporter =
            defaultMetricsReporterFactory.createMetricsReporter(metricsConfig.toUrl());
    defaultMetricsReporter.init();
    applicationModel.getBeanFactory().registerBean(defaultMetricsReporter);
}

dubbo 在 metrics 部分的设计使用了非常多的 SPI 扩展能力,包括 MetricsReporterFactory、MetricsCollector 等等,dubbo 作为一个通信框架,使用基于 SPI 而不是 AutoConfiguration (springboot 中的机制),应该也是不期望框架本身过度耦合 Springboot。

基于事件驱动的埋点逻辑链路

dubbo 指标采集的整体设计思路基于事件驱动编程思想,其大体的事件处理链路如下(参考官方提供):

上面这张图,笔者一开始在分析的过程中其实有不少问题;从消息总线到指标转发器,再到指标转发器/收集器,最后到指标监听器,事件的流转和二次发布理解起来还是有一些成本在的;在 debug 过程中,结合下面线程堆栈,自下而上,第一次事件发布,消费者是在收集器;第二次事件发布,消费者是监听器,最后再由收集器转存到 BaseStatComposite 中去。

因此这里对上图进行补充,将存储和 export&reporter 也放进来,形成一个完整的结构图

扩展 OTLP 协议

笔者在 架构解析系列-OTeL & Micrometer 在 Spring Boot 中的应用与分析 这篇文中分析了 springboot 中对于 OTeL & Micrometer 的使用分析,springboot 提供的 埋点机制和 dubbo 中提供的是一样的,底层都是基于 Micrometer api,上层协议透出主要是依赖于 Micrometer 提供的不同sdk,主流的就是 prometheus 和 otlp。在 dubbo 社区的 这个提案 issue 中,已经提供了一些思路,笔者基于 dubbo 3.2.x 版本进行了初步的集成和测试,目前已经提交社区进行讨论,期待进一步的沟通和 approve。

因为 prometheus 和 otlp 使用的是两种不同的数据推送方式,prometheus 使用的是 pull 的方式,即提供一个 endpoint 出去(PushGateway 也是基于 push);而 otlp 的实现则是通过 push 的方式。在推送的数据量中,prometheus 协议一次性推送了全量数据,otlp 则是按批推送。

总结

本篇的出发点是这个提案 issue,期望通过洞悉 dubbo 埋点的内部实现来找到 otlp 协议吐出的切入点。整体看来,dubbo 的 metrics 的实现逻辑还是比较清晰的,而且官方文档中对于 metrics 部分的源码分析也非常详细。笔者在参阅了官方文档和结合自己理解以及逐步 debug 逻辑,也基本摸清了dubbo 的实现逻辑,本篇作为学习备忘记录下来,也分享给更多的同学。

相关推荐
杨荧17 分钟前
【JAVA毕业设计】基于Vue和SpringBoot的宠物咖啡馆平台
java·开发语言·jvm·vue.js·spring boot·spring cloud·开源
Firechou23 分钟前
SpringBoot+MyBatis+MySQL的Point实现范围查找
spring boot·mysql·mybatis·point·范围查找·附近查找
喜欢打篮球的普通人32 分钟前
rust高级特征
开发语言·后端·rust
苹果酱05671 小时前
C语言 char 字符串 - C语言零基础入门教程
java·开发语言·spring boot·mysql·中间件
程序员小潘1 小时前
Dubbo分布式日志跟踪实现
分布式·dubbo
代码小鑫1 小时前
A032-基于Spring Boot的健康医院门诊在线挂号系统
java·开发语言·spring boot·后端·spring·毕业设计
bjxiaxueliang1 小时前
一文详解MacOS使用VSCode搭建SpringBoot+Gradle开发环境
spring boot·vscode·macos
豌豆花下猫2 小时前
REST API 已经 25 岁了:它是如何形成的,将来可能会怎样?
后端·python·ai
喔喔咿哈哈2 小时前
【手撕 Spring】 -- Bean 的创建以及获取
java·后端·spring·面试·开源·github
码农小丘2 小时前
了解springboot国际化用途以及使用
java·spring boot·spring