架构解析系列-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 的实现逻辑,本篇作为学习备忘记录下来,也分享给更多的同学。

相关推荐
一弓虽12 分钟前
SpringBoot 学习
java·spring boot·后端·学习
姑苏洛言21 分钟前
扫码小程序实现仓库进销存管理中遇到的问题 setStorageSync 存储大小限制错误解决方案
前端·后端
光而不耀@lgy36 分钟前
C++初登门槛
linux·开发语言·网络·c++·后端
方圆想当图灵1 小时前
由 Mybatis 源码畅谈软件设计(七):SQL “染色” 拦截器实战
后端·mybatis·代码规范
毅航1 小时前
MyBatis 事务管理:一文掌握Mybatis事务管理核心逻辑
java·后端·mybatis
我的golang之路果然有问题2 小时前
速成GO访问sql,个人笔记
经验分享·笔记·后端·sql·golang·go·database
柏油2 小时前
MySql InnoDB 事务实现之 undo log 日志
数据库·后端·mysql
来自星星的猫教授2 小时前
spring,spring boot, spring cloud三者区别
spring boot·spring·spring cloud
乌夷3 小时前
使用spring boot vue 上传mp4转码为dash并播放
vue.js·spring boot·dash
写bug写bug3 小时前
Java Streams 中的7个常见错误
java·后端