我们正在使用 Quarkus Jakarta 构建一个自定义的 Prometheus 导出器,重点是避免高基数并确保高效的指标。准备好迎接一些指标魔法吧!

为什么要自定义导出器?我们不是在重新发明轮子吗?

在深入探讨之前,让我们先解决一个问题:为什么要费心自定义导出器,而不是使用现成的解决方案?

  • 定制化指标:您的应用程序是独特的,有时您需要的指标并不是现成的。
  • 性能优化:自定义导出器可以让您微调测量内容,可能减少开销。
  • 避免指标爆炸:强大的能力伴随着巨大的责任——以及避免高基数陷阱的能力。

设置 Quarkus Jakarta 项目

首先,让我们启动并运行我们的 Quarkus 项目。如果您是 Quarkus 的新手,可以将其视为 Jakarta EE 的超级英雄版本——比子弹还快,比火车还强大。

使用以下 Maven 命令创建一个新的 Quarkus 项目:

mvn io.quarkus:quarkus-maven-plugin:2.16.5.Final:create \
    -DprojectGroupId=com.example \
    -DprojectArtifactId=custom-prometheus-exporter \
    -DclassName="com.example.ExporterResource" \
    -Dpath="/exporter"

现在,让我们将必要的依赖项添加到我们的 pom.xml 中:

<dependencies>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-resteasy-reactive</artifactId>
    </dependency>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-micrometer-registry-prometheus</artifactId>
    </dependency>
</dependencies>

核心问题:构建导出器

现在我们已经设置好了项目,让我们创建我们的自定义导出器。我们将专注于一个假设场景,即我们正在监控一个复杂的电子商务系统。

package com.example;

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import java.util.Random;

@Path("/exporter")
public class ExporterResource {

    @Inject
    MeterRegistry registry;

    private final Random random = new Random();

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        // 模拟一些指标
        recordOrderMetrics();
        recordUserMetrics();
        return "指标已更新!";
    }

    private void recordOrderMetrics() {
        // 不记录每个产品的指标(高基数),
        // 而是记录每个类别的汇总指标
        String[] categories = {"Electronics", "Clothing", "Books", "Home"};
        for (String category : categories) {
            double orderValue = 100 + random.nextDouble() * 900; // 随机订单值在 100 到 1000 之间
            registry.gauge("ecommerce.order.value", Tags.of("category", category), orderValue);
        }
    }

    private void recordUserMetrics() {
        // 不记录每个用户的指标,而是使用桶
        int activeUsers = 1000 + random.nextInt(9000); // 随机数在 1000 到 10000 之间
        String userBucket = activeUsers < 5000 ? "low" : "high";
        registry.gauge("ecommerce.active.users", Tags.of("load", userBucket), activeUsers);
    }
}

避免高基数陷阱

高基数是 Prometheus 设置中的噩梦。就像邀请您城市中的每个人参加家庭聚会——事情肯定会失控。以下是我们避免这种情况的方法:

  • 分类:与其跟踪每个产品的指标(可能有数千个),不如将它们分组到类别中。
  • 分桶:对于用户指标,我们使用简单的“低”或“高”桶,而不是单独跟踪每个用户。

记住,目标是拥有足够详细的信息以便有用,而不是淹没在数据中。

确保高效的指标

效率不仅仅是避免高基数。以下是一些保持指标精简的其他技巧:

  1. 谨慎使用标签:标签很强大,但如果使用过多,可能会导致指标爆炸。
  2. 尽可能汇总:有时总和或平均值比单个数据点更有用。
  3. 选择合适的指标类型:计量器、计数器和直方图各有其用途。明智地使用它们。
  4. 设置保留策略:并非所有指标都需要永久保留。在 Prometheus 中设置适当的保留期。

测试您的导出器

在我们自我表扬之前,让我们确保这个东西确实有效。运行您的 Quarkus 应用程序:

./mvnw quarkus:dev

现在,访问端点以生成一些指标:

curl http://localhost:8080/exporter

最后,查看您的指标:

curl http://localhost:8080/q/metrics

您应该会看到类似这样的内容:

# HELP ecommerce_order_value 
# TYPE ecommerce_order_value gauge
ecommerce_order_value{category="Electronics"} 543.21
ecommerce_order_value{category="Clothing"} 321.54
ecommerce_order_value{category="Books"} 123.45
ecommerce_order_value{category="Home"} 987.65

# HELP ecommerce_active_users 
# TYPE ecommerce_active_users gauge
ecommerce_active_users{load="high"} 7523.0

证据在于布丁:分析您的指标

现在我们已经启动并运行了导出器,让我们来谈谈可以用这些指标做些什么。以下是一些您可能会发现有用的 Prometheus 查询:

# 所有类别的平均订单值
avg(ecommerce_order_value)

# 活跃用户的总数
sum(ecommerce_active_users)

# 按类别的最高订单值
max(ecommerce_order_value) by (category)

这些查询让您了解从精心制作的指标中可以获得的见解。

总结:经验教训

构建自定义 Prometheus 导出器不仅仅是简单地组合一些指标然后就完事了。这是一门艺术,需要仔细考虑您正在测量的内容以及如何测量。以下是关键要点:

  • 始终警惕高基数。它是 Prometheus 设置的无声杀手。
  • 尽可能汇总和分类,以保持指标的意义和可管理性。
  • 谨慎选择标签。它们很强大,但强大的能力伴随着巨大的责任。
  • 测试、迭代和改进。您的第一次尝试可能不会完美,这没关系。

记住,目标是拥有提供可操作见解的指标,而不仅仅是一堆数字。祝您监控愉快!

思考的食粮

“计算的目标是洞察,而不是数字。” - Richard Hamming

当您继续完善自定义导出器时,请记住这句话。关键不在于您可以生成多少指标,而在于您可以从中获得多少见解。

附加资源

想深入了解?查看这些资源:

现在去像专业人士一样导出那些指标吧!记住,在监控的世界中,少即是多。除非我们在谈论正常运行时间——那时多就是多。