Dubbo 3.3可观测性指标Micrometer Tag自定义:DubboMetrics与MetricFilter

好的,我们现在开始。

Dubbo 3.3 可观测性指标 Micrometer Tag 自定义:DubboMetrics 与 MetricFilter

大家好,今天我们来深入探讨 Dubbo 3.3 中可观测性指标 Micrometer Tag 自定义,重点讲解 DubboMetricsMetricFilter 的使用。可观测性对于分布式系统的稳定运行至关重要,而 Dubbo 作为流行的 RPC 框架,提供了丰富的可观测性支持。通过自定义 Micrometer Tag,我们可以更精细地监控 Dubbo 服务的运行状态,从而更好地诊断和解决问题。

1. DubboMetrics 概述

DubboMetrics 是 Dubbo 集成 Micrometer 的核心组件之一。它负责收集 Dubbo 运行时的各种指标,并将这些指标注册到 Micrometer 的 MeterRegistry 中。这些指标可以被 Prometheus、InfluxDB 等监控系统采集,从而实现对 Dubbo 服务的监控。

DubboMetrics 默认会收集一些基本的指标,例如:

  • dubbo.consumer.invocations.total: Consumer 端总的调用次数。
  • dubbo.consumer.invocations.active: Consumer 端当前活跃的调用次数。
  • dubbo.consumer.invocations.failed: Consumer 端调用失败的次数。
  • dubbo.provider.invocations.total: Provider 端总的调用次数。
  • dubbo.provider.invocations.active: Provider 端当前活跃的调用次数。
  • dubbo.provider.invocations.failed: Provider 端调用失败的次数。
  • dubbo.provider.process.time: Provider 端处理请求的时间。

除了这些基本指标,DubboMetrics 还允许我们自定义 Tag,以便更细粒度地分析指标数据。例如,我们可以根据服务接口、方法名、应用名等信息来添加 Tag。

2. MetricFilter 概述

MetricFilter 是 Dubbo 提供的一个 SPI 接口,用于在 DubboMetrics 收集指标时添加自定义的 Tag。通过实现 MetricFilter 接口,我们可以拦截 Dubbo 的调用请求,并根据请求的上下文信息来添加 Tag。

MetricFilter 接口定义如下:

package org.apache.dubbo.metrics.filter;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.SPI;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcException;

import java.util.List;

@SPI
public interface MetricFilter {

    /**
     * Add metrics tags.
     *
     * @param invoker     service invoker
     * @param invocation  service invocation
     * @param url         service url
     * @param metricList  Metric list
     * @param isProvider  is provider
     */
    void addTags(Invoker<?> invoker, Invocation invocation, URL url, List<String> metricList, boolean isProvider);

    /**
     * Before invoke.
     *
     * @param invoker     service invoker
     * @param invocation  service invocation
     * @param url         service url
     * @param isProvider  is provider
     */
    void beforeInvoke(Invoker<?> invoker, Invocation invocation, URL url, boolean isProvider);

    /**
     * After invoke.
     *
     * @param invoker     service invoker
     * @param invocation  service invocation
     * @param url         service url
     * @param result      service result
     * @param isProvider  is provider
     */
    void afterInvoke(Invoker<?> invoker, Invocation invocation, URL url, Result result, boolean isProvider);

    /**
     * On invoke exception.
     *
     * @param invoker     service invoker
     * @param invocation  service invocation
     * @param url         service url
     * @param exception   service exception
     * @param isProvider  is provider
     */
    void onInvokeException(Invoker<?> invoker, Invocation invocation, URL url, Throwable exception, boolean isProvider);
}

该接口定义了五个方法:

  • addTags(): 用于添加自定义 Tag。该方法接收 InvokerInvocationURLmetricListisProvider 作为参数。Invoker 代表服务调用者,Invocation 代表调用信息,URL 代表服务的 URL,metricList 是一个字符串列表,用于存储要添加的 Tag,isProvider 表示当前是否为 Provider 端。
  • beforeInvoke(): 在服务调用之前执行。
  • afterInvoke(): 在服务调用之后执行。
  • onInvokeException(): 在服务调用发生异常时执行。

我们可以通过实现 MetricFilter 接口,并实现 addTags 方法来添加自定义 Tag。同时,也可以利用 beforeInvokeafterInvokeonInvokeException 方法来收集一些额外的指标信息。

3. 自定义 MetricFilter 实现

下面我们通过一个例子来说明如何自定义 MetricFilter 来添加自定义 Tag。假设我们需要根据请求的参数来添加 Tag。

首先,我们需要创建一个类来实现 MetricFilter 接口:

package com.example.dubbo.metrics;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.metrics.filter.MetricFilter;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcException;

import java.util.List;

@Activate(group = {"consumer", "provider"})
public class CustomMetricFilter implements MetricFilter {

    @Override
    public void addTags(Invoker<?> invoker, Invocation invocation, URL url, List<String> metricList, boolean isProvider) {
        String parameter = invocation.getArgument(0, String.class); // 假设第一个参数是 String 类型
        if (parameter != null) {
            metricList.add("parameter=" + parameter);
        }
    }

    @Override
    public void beforeInvoke(Invoker<?> invoker, Invocation invocation, URL url, boolean isProvider) {
        // 可以添加一些前置处理逻辑
    }

    @Override
    public void afterInvoke(Invoker<?> invoker, Invocation invocation, URL url, Result result, boolean isProvider) {
        // 可以添加一些后置处理逻辑
    }

    @Override
    public void onInvokeException(Invoker<?> invoker, Invocation invocation, URL url, Throwable exception, boolean isProvider) {
        // 可以添加一些异常处理逻辑
    }
}

在这个例子中,我们实现了 addTags 方法,从 Invocation 中获取第一个参数,并将其作为 Tag 添加到 metricList 中。 @Activate(group = {"consumer", "provider"}) 注解表示该 MetricFilter 将在 Consumer 端和 Provider 端都生效。

接下来,我们需要将这个 MetricFilter 注册到 Dubbo 中。 Dubbo 使用 SPI (Service Provider Interface) 机制来加载 MetricFilter。 因此,我们需要在 src/main/resources/META-INF/dubbo/org.apache.dubbo.metrics.filter.MetricFilter 文件中添加以下内容:

custom=com.example.dubbo.metrics.CustomMetricFilter

这样,Dubbo 启动时就会加载 CustomMetricFilter,并在收集指标时调用 addTags 方法。

4. 配置 Micrometer

为了使 Dubbo 的指标能够被监控系统采集,我们需要配置 Micrometer。 Dubbo 3.3 默认使用 Micrometer 作为指标收集工具,因此我们需要添加 Micrometer 的依赖。

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-core</artifactId>
    <version>1.10.0</version> <!-- 使用最新版本 -->
</dependency>

<!-- Prometheus MeterRegistry -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
    <version>1.10.0</version> <!-- 使用最新版本 -->
</dependency>

然后,我们需要配置 Micrometer 的 MeterRegistry。 可以通过 Spring Boot 的配置来完成:

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MetricsConfiguration {

    @Bean
    public MeterRegistry meterRegistry() {
        return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
    }
}

这段代码创建了一个 PrometheusMeterRegistry,并将它注册为 Spring Bean。 这样,Dubbo 就会将指标注册到 PrometheusMeterRegistry 中,然后 Prometheus 就可以从 PrometheusMeterRegistry 中采集指标数据。

5. 验证自定义 Tag

配置完成后,我们可以启动 Dubbo 服务,并调用服务接口。 然后,我们可以通过 Prometheus 的 Web 界面来查看指标数据。

假设我们的服务接口如下:

public interface DemoService {
    String sayHello(String name);
}

并且我们在 Consumer 端调用 sayHello 方法,传入的参数是 "World"。 那么,我们应该能够在 Prometheus 中看到包含 parameter=World Tag 的指标数据。

例如,dubbo_consumer_invocations_total{interface="com.example.DemoService", method="sayHello", parameter="World", ...}

6. 更复杂的 MetricFilter 实现

除了根据请求参数添加 Tag,我们还可以根据其他信息来添加 Tag。 例如,我们可以根据用户的身份信息来添加 Tag,从而实现对不同用户的请求进行监控。

package com.example.dubbo.metrics;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.metrics.filter.MetricFilter;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcException;

import java.util.List;
import java.util.Map;

@Activate(group = {"consumer", "provider"})
public class UserMetricFilter implements MetricFilter {

    @Override
    public void addTags(Invoker<?> invoker, Invocation invocation, URL url, List<String> metricList, boolean isProvider) {
        Map<String, Object> attachments = RpcContext.getContext().getObjectAttachments();
        String userId = (String) attachments.get("userId");
        if (userId != null) {
            metricList.add("userId=" + userId);
        }
    }

    @Override
    public void beforeInvoke(Invoker<?> invoker, Invocation invocation, URL url, boolean isProvider) {
        // 可以添加一些前置处理逻辑
    }

    @Override
    public void afterInvoke(Invoker<?> invoker, Invocation invocation, URL url, Result result, boolean isProvider) {
        // 可以添加一些后置处理逻辑
    }

    @Override
    public void onInvokeException(Invoker<?> invoker, Invocation invocation, URL url, Throwable exception, boolean isProvider) {
        // 可以添加一些异常处理逻辑
    }
}

在这个例子中,我们从 RpcContext 中获取 userId,并将其作为 Tag 添加到 metricList 中。 使用 RpcContext 需要注意在调用服务前设置 attachment。 例如,在 Consumer 端:

RpcContext.getContext().setAttachment("userId", "123");
String result = demoService.sayHello("World");

7. 总结

通过自定义 MetricFilter,我们可以灵活地添加自定义 Tag,从而更细粒度地监控 Dubbo 服务的运行状态。 这对于诊断和解决问题非常有帮助。 我们可以根据实际需求,添加不同的 Tag,例如根据请求参数、用户身份、业务类型等信息来添加 Tag。 结合 Micrometer 和 Prometheus 等监控系统,我们可以构建完善的可观测性体系,从而更好地保障 Dubbo 服务的稳定运行。

8. 扩展思路

以下是一些扩展思路,可用于更深入地利用 DubboMetricsMetricFilter 进行可观测性建设:

  • 动态 Tag 配置: 将 Tag 配置从代码中解耦出来,通过配置文件或配置中心动态调整 Tag 的添加规则。
  • 采样策略: 对于高并发场景,可以实现采样策略,只对部分请求添加 Tag,以减少性能开销。
  • 自定义指标: 除了添加 Tag,还可以自定义指标,例如统计特定业务逻辑的执行时间。
  • 结合链路追踪: 将 Micrometer 指标与链路追踪系统(例如 SkyWalking)结合,可以更全面地了解请求的调用链和性能瓶颈。

9. 常见问题与注意事项

  • 性能影响: 添加过多的 Tag 可能会影响性能,需要根据实际情况进行权衡。
  • Tag 命名规范: 建议使用统一的 Tag 命名规范,方便查询和分析指标数据。
  • 避免敏感信息: 避免将敏感信息添加到 Tag 中,例如密码、身份证号等。
  • MetricFilter 的顺序: 如果定义了多个 MetricFilter,它们的执行顺序是不确定的,需要注意它们之间的依赖关系。

10. 代码示例

以下是一个完整的代码示例,演示了如何自定义 MetricFilter 并配置 Micrometer:

1. 服务接口 (DemoService.java):

package com.example.dubbo;

public interface DemoService {
    String sayHello(String name);
}

2. 服务实现 (DemoServiceImpl.java):

package com.example.dubbo.impl;

import com.example.dubbo.DemoService;
import org.apache.dubbo.config.annotation.DubboService;

@DubboService
public class DemoServiceImpl implements DemoService {
    @Override
    public String sayHello(String name) {
        return "Hello, " + name;
    }
}

3. 自定义 MetricFilter (CustomMetricFilter.java):

package com.example.dubbo.metrics;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.metrics.filter.MetricFilter;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcException;

import java.util.List;
import java.util.Map;

@Activate(group = {"consumer", "provider"})
public class CustomMetricFilter implements MetricFilter {

    @Override
    public void addTags(Invoker<?> invoker, Invocation invocation, URL url, List<String> metricList, boolean isProvider) {
        Map<String, Object> attachments = RpcContext.getContext().getObjectAttachments();
        String userId = (String) attachments.get("userId");
        if (userId != null) {
            metricList.add("userId=" + userId);
        }
    }

    @Override
    public void beforeInvoke(Invoker<?> invoker, Invocation invocation, URL url, boolean isProvider) {
        // 可以添加一些前置处理逻辑
    }

    @Override
    public void afterInvoke(Invoker<?> invoker, Invocation invocation, URL url, Result result, boolean isProvider) {
        // 可以添加一些后置处理逻辑
    }

    @Override
    public void onInvokeException(Invoker<?> invoker, Invocation invocation, URL url, Throwable exception, boolean isProvider) {
        // 可以添加一些异常处理逻辑
    }
}

4. MetricFilter 配置文件 (src/main/resources/META-INF/dubbo/org.apache.dubbo.metrics.filter.MetricFilter):

custom=com.example.dubbo.metrics.CustomMetricFilter

5. Micrometer 配置 (MetricsConfiguration.java):

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MetricsConfiguration {

    @Bean
    public MeterRegistry meterRegistry() {
        return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
    }
}

6. Spring Boot 启动类 (DubboApplication.java):

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DubboApplication {

    public static void main(String[] args) {
        SpringApplication.run(DubboApplication.class, args);
    }
}

7. Consumer 端调用示例:

import com.example.dubbo.DemoService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.rpc.RpcContext;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class ConsumerRunner implements CommandLineRunner {

    @DubboReference
    private DemoService demoService;

    @Override
    public void run(String... args) throws Exception {
        RpcContext.getContext().setAttachment("userId", "123");
        String result = demoService.sayHello("World");
        System.out.println(result);
    }
}

8. Maven 依赖 (pom.xml):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>dubbo-metrics</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>dubbo-metrics</name>
    <description>dubbo-metrics</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>3.3.0</version>
        </dependency>

        <!-- Zookeeper dependencies -->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>5.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>5.1.0</version>
        </dependency>

        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-core</artifactId>
            <version>1.10.0</version>
        </dependency>

        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-registry-prometheus</artifactId>
            <version>1.10.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

11. 总结:自定义Tag,提升可观测性

本文详细介绍了 Dubbo 3.3 中 DubboMetricsMetricFilter 的使用,以及如何自定义 Tag 来更精细地监控 Dubbo 服务的运行状态。 通过代码示例,展示了如何实现自定义 MetricFilter,并配置 Micrometer 和 Prometheus。 通过自定义 Tag,可以实现更精细的可观测性,帮助我们更好地诊断和解决 Dubbo 服务的问题。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注