news 2026/6/9 9:05:50

Java写的CloudWatch指标导出器,让Prometheus轻松采集AWS监控数据

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java写的CloudWatch指标导出器,让Prometheus轻松采集AWS监控数据

本文还有配套的精品资源,点击获取

简介:一个用Java开发的轻量级工具,能把AWS CloudWatch里的各种监控指标(比如EC2 CPU使用率、RDS连接数、Lambda调用次数等)实时转换成Prometheus能直接抓取的格式。支持Java 8+,编译用mvn package,运行就一条java -jar命令,指定端口和YAML配置文件(自带example.yml参考),默认走9106端口。认证走AWS标准凭证链:环境变量(AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY)、IAM角色、~/.aws/credentials文件都行;最小权限只要cloudwatch:ListMetrics和cloudwatch:GetMetricStatistics,如果要用标签筛选功能(aws_tag_select),再加个tag:GetResources就行。配置全靠YAML,可以按命名空间、指标名、维度(如InstanceId、LoadBalancerName)、统计方式(Average/Sum/Maximum)、采样周期(比如5分钟)和抓取间隔灵活设置。带Dockerfile,build完就能docker run跑起来;项目结构规范,含完整Maven配置(pom.xml)、源码(src/main)、测试(src/test)、许可证(LICENSE)、说明文档(README.md)、贡献指南(CONTRIBUTING.md)和安全策略(SECURITY.md),适合嵌入现有Prometheus监控体系,也方便二次开发或定制指标采集逻辑。

1. 项目概述:为什么我们需要一个“CloudWatch导出器”?

在 AWS 上跑生产服务的团队,几乎没人能绕开 CloudWatch——它是云上最原生、最全面的监控数据源:EC2 的 CPUUtilization、RDS 的 DatabaseConnections、ALB 的 HTTPCode_ELB_5XX_Count、Lambda 的 Invocations 和 Duration、ECS 任务的 MemoryUtilizationPercent……这些指标每天都在产生海量时序数据。但问题来了:如果你的监控栈已经统一用 Prometheus + Grafana,那 CloudWatch 就成了一个“数据孤岛”。你没法直接在 Grafana 里写aws_ec2_cpu_utilization{instance_id="i-0a1b2c3d4e5f67890"}这样的 PromQL 查询,更没法把 CloudWatch 指标和你自定义的 JVM 指标(比如jvm_memory_used_bytes)放在同一张看板里做关联分析。

这时候,官方方案是 CloudWatch Exporter for Prometheus(由 AWS Labs 维护),但它用的是 Go 编写,配置偏静态,对 Java 技术栈团队来说,调试、定制、打 patch 都不够“顺手”。而我们今天聊的这个项目,就是为 Java 工程师量身打造的一套解法:它不是一个黑盒二进制,而是一个可读、可调、可 debug、可嵌入、可扩展的 Java 工程。它不替代 CloudWatch,而是做它的“翻译官”——把 CloudWatch API 返回的 JSON 响应,实时转换成 Prometheus 的/metrics端点能吐出的文本格式(即 OpenMetrics 格式)。你不需要改 Prometheus 配置去对接 AWS,只需要把它当成一个标准的 exporter 加进scrape_configs,剩下的交给它。

关键词里的“CloudWatch导出器”不是泛泛而谈,它特指一种协议桥接层:一边是 AWS SDK v2 的异步 HTTP 客户端,另一边是 Prometheus Java Client 的 CollectorRegistry;中间是 YAML 驱动的指标发现逻辑与采样调度引擎。而“Prometheus Java客户端”这个标签,恰恰点出了它的技术底色——它没有自己造轮子去实现指标注册、Gauge/Counter 封装、HTTP 暴露逻辑,而是深度集成io.prometheus:simpleclient_httpserverio.prometheus:simpleclient_hotspot,这意味着它天生支持 JVM 自身指标(GC 次数、线程数、堆内存使用率)的自动暴露,你甚至可以在同一个端口下同时看到cloudwatch_cpu_utilizationjvm_gc_collection_seconds_count,这对排查“到底是业务慢还是 JVM 卡顿”这类混合故障特别关键。

至于“AWS指标采集”,它解决的从来不是“能不能采”,而是“怎么采得稳、采得准、采得省”。比如:你不会想让 exporter 每 15 秒就全量 ListMetrics 一次(那会触发 API 限流);也不会希望所有 EC2 实例的 NetworkIn 指标都用 1 分钟粒度去 GetMetricStatistics(成本翻倍且无意义);更不想因为某个命名空间里突然多了几百个新指标,就把整个 exporter 的内存撑爆。这个 Java 导出器的设计哲学,就是把“可控性”刻进每一行代码:采样间隔、统计周期、维度过滤、并发请求数、失败重试策略、缓存 TTL……全部可配、可观察、可降级。它不是一个“开箱即用就完事”的玩具,而是一个你愿意放进 CI/CD 流水线、写单元测试、加 Jaeger 链路追踪、甚至在生产环境里开着 JFR 录制 GC 行为的正经服务。

我去年在给一家做跨境支付的客户做监控体系升级时,就踩过没这种导出器的坑。他们当时用的是 Python 写的简易脚本轮询 CloudWatch,结果某天 RDS 命名空间新增了 200+ 个 Performance Insights 指标,脚本没做任何限流和缓存,直接把 CloudWatch API 调用频率干到每秒 80+ 次,触发了账户级限流,连带影响了 CloudTrail 日志投递。后来换成这个 Java 版本,光靠配置里的cache_ttl: 300(5 分钟缓存 ListMetrics 结果)和max_concurrent_requests: 5两个参数,就把峰值 QPS 压到了 3 以下,而且还能通过/actuator/prometheus端点实时看到cloudwatch_list_metrics_totalcloudwatch_get_metric_statistics_errors_total这类内部指标,真正做到了“可观测即运维”。

2. 整体架构与设计思路拆解

这个导出器不是简单地把 AWS SDK 调用封装成一个 HTTP handler,它的核心价值在于三层抽象:发现层(Discovery)→ 采集层(Collection)→ 暴露层(Exposition)。这三层之间完全解耦,靠事件驱动和配置驱动串联,这也是它能兼顾灵活性与稳定性的根本原因。

2.1 发现层:动态指标发现,而非静态硬编码

很多初版 CloudWatch 导出器会要求你在 YAML 里手动列出所有要采集的指标,比如:

metrics: - namespace: "AWS/EC2" metric_name: "CPUUtilization" dimensions: - name: "InstanceId" value: "i-0a1b2c3d4e5f67890"

这在测试环境没问题,但一上生产就崩:EC2 实例是自动伸缩组(ASG)动态创建的,RDS 实例可能按周滚动重建,ALB 名字更是随 CI/CD 流水线生成。硬编码等于放弃自动化。这个 Java 版本的破局点,是引入了CloudWatch ListMetrics API 的智能分页与缓存机制

它的工作流程是这样的:
1. 启动时,根据配置中的namespaces列表(如["AWS/EC2", "AWS/RDS"]),并发发起ListMetricsRequest
2. 对每个命名空间,它不是一次性拉取全部指标(CloudWatch 默认只返回 500 条),而是自动处理分页令牌(nextToken),直到遍历完该命名空间下所有指标;
3. 关键来了:它会对返回的指标列表做两层过滤——先按metric_name白名单(如["CPUUtilization", "NetworkIn", "DatabaseConnections"])筛,再按dimensions模式匹配(支持通配符*和正则表达式,比如dimension_pattern: "^(InstanceId|DBInstanceIdentifier)$");
4. 最终生成一个内存中的MetricDefinition清单,每条记录包含:完整命名空间、指标名、维度组合(如[{"name":"InstanceId","value":"i-0a1b2c3d4e5f67890"}])、统计方式(Average)、周期(300秒)等元信息;
5. 这个清单会被写入一个带 TTL 的 Guava Cache(默认 5 分钟),后续采集请求都从缓存读,避免高频 ListMetrics。

提示:example.ymldiscovery.cache_ttl: 300这个参数,不是随便设的。CloudWatch 的指标元数据变更频率很低(通常以小时计),设成 300 秒既能保证新鲜度,又能把 ListMetrics 调用频次压到最低。如果你的业务有“分钟级动态创建资源”的场景(比如 Fargate 任务),可以调小到 60,但务必配合discovery.max_retries: 2防止缓存击穿。

2.2 采集层:异步、批处理、带背压的指标拉取

发现只是第一步,真正的性能瓶颈在采集。CloudWatch 的GetMetricStatistics是一个昂贵的 API:每次调用最多只能查 1 个指标 + 1 组维度 + 1 个统计周期,且返回的数据点数量受时间范围限制(最长 14 天,最多 1440 个点)。如果对 100 个 EC2 实例的 CPU 指标逐个串行调用,耗时会轻松突破 30 秒,远超 Prometheus 默认 10 秒抓取超时。

这个 Java 导出器的应对策略是“三管齐下”:
-异步非阻塞:底层用的是 AWS SDK v2 的CloudWatchAsyncClient,所有GetMetricStatistics调用都是 CompletableFuture 异步发起,线程不阻塞;
-批量合并:它会把同一命名空间、同一指标名、同一统计方式、同一周期的请求,按维度值进行哈希分组(比如所有AWS/EC2/CPUUtilization的请求,按InstanceId分成 N 组),然后对每组维度并发发起请求(最大并发数由collection.max_concurrent_requests控制,默认 10);
-智能背压:当CompletableFuture.allOf()检测到某批次请求中超过 30% 失败,或平均响应时间超过 5 秒,它会自动触发降级——跳过本次采集,复用上一轮缓存的指标值,并记录cloudwatch_collection_degraded_total计数器。

这里有个容易被忽略的细节:collection.sample_interval(采样间隔)和cloudwatch.period(CloudWatch 数据周期)不是一回事。前者是 exporter 主动发起采集的频率(比如每 60 秒拉一次最新数据),后者是你要从 CloudWatch 获取哪个粒度的历史数据(比如period: 300表示查最近 5 分钟的平均值)。它们的关系是:sample_interval应该 ≥period,否则会出现数据覆盖或空洞。example.yml里设的sample_interval: 60period: 300,意味着它每分钟去 CloudWatch 查一次“过去 5 分钟的平均 CPU 使用率”,这样既能保证数据新鲜(延迟 ≤ 60 秒),又不会因频繁查询小周期数据而推高成本。

2.3 暴露层:原生 Prometheus 兼容,不止于指标

暴露层看似最简单(不就是启动一个 HTTP Server 吗?),但恰恰是它决定了你能否真正融入现有监控生态。这个导出器用的是io.prometheus:simpleclient_httpserver,但它做了两处关键增强:

第一,多端点支持:除了标准的/metrics(暴露 CloudWatch 指标 + JVM 指标),它还内置了/health(返回{ "status": "UP", "cloudwatch": "UP" })和/actuator/prometheus(Spring Boot Actuator 风格,兼容 Prometheus Operator 的 ServiceMonitor 探针)。更重要的是,它把自身运行时的关键指标也注册进了 CollectorRegistry:
-cloudwatch_discovery_duration_seconds:ListMetrics 耗时直方图
-cloudwatch_collection_duration_seconds:GetMetricStatistics 耗时直方图
-cloudwatch_collection_errors_total:按错误类型(ThrottlingException,InvalidParameterValue,AccessDenied)分组的计数器
-exporter_uptime_seconds:进程启动时长(Gauge)

第二,指标命名与标签规范化:CloudWatch 原生指标名带斜杠(如AWS/EC2/CPUUtilization),Prometheus 不允许,所以它自动转换为下划线(aws_ec2_cpu_utilization)。维度值(如InstanceId=i-0a1b2c3d4e5f67890)会被转为 Prometheus 标签,但会做安全清洗:去掉非法字符(/,:, ),长度截断(默认 64 字节),并添加cloudwatch_dimension_前缀避免和业务标签冲突。example.yml里的label_prefix: "cw_"就是控制这个前缀的。

注意:如果你启用了aws_tag_select功能(通过tag:GetResources权限获取资源标签),它会把Name=prod-db,Environment=prod这类标签也作为 Prometheus 标签注入,但会强制加上tag_前缀(如tag_Name="prod-db"),这是为了和原始维度标签严格区分开,防止 label cardinality 爆炸。

3. 核心配置解析与实操要点

YAML 配置是这个导出器的“大脑”,它不像某些工具那样只有几个开关,而是提供了一套完整的指标治理 DSL。我们来逐段拆解example.yml的真实含义,并告诉你哪些参数必须改、哪些可以不动、哪些改了会踩大坑。

3.1 全局配置:端口、认证、日志

server: port: 9106 context_path: "/" aws: region: "us-east-1" credentials: type: "default" # 可选: default, static, profile, container # static 下才需要 access_key_id / secret_access_key # profile 下才需要 profile_name

server.port是最直观的,但context_path很关键。如果你打算用 Nginx 做反向代理(比如把https://monitor.example.com/cloudwatch/代理到后端http://exporter:9106/),就必须把context_path设为/cloudwatch/,否则/metrics请求会 404。我见过太多人卡在这一步,最后发现是路径没对齐。

aws.credentials.type是认证模式开关。default表示走 AWS 标准凭证链(推荐),它会按顺序检查:
1. 环境变量AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY
2. ECS Task Role 或 EC2 Instance Profile(IAM 角色)
3.~/.aws/credentials文件里的defaultprofile
4.~/.aws/config文件里的defaultprofile(带region

如果你在 Kubernetes 里部署,强烈建议用 IAM Roles for Service Accounts(IRSA),即让 Pod 的 ServiceAccount 绑定一个 IAM Role,这样连环境变量都不用设,最安全。type: "static"只应在开发测试时用,生产环境绝对禁止硬编码 AKSK。

3.2 发现配置:精准定位你要的指标

discovery: cache_ttl: 300 max_retries: 2 namespaces: - "AWS/EC2" - "AWS/RDS" - "AWS/ELB" metric_names: - "CPUUtilization" - "NetworkIn" - "DatabaseConnections" - "HTTPCode_ELB_5XX_Count" dimension_patterns: - "^(InstanceId|DBInstanceIdentifier|LoadBalancerName)$"

这段配置决定了“导出器知道哪些指标存在”。namespacesmetric_names是白名单,必须精确匹配 CloudWatch 控制台里看到的名字(大小写敏感!)。dimension_patterns是正则表达式,用来筛选维度名。注意:它匹配的是维度InstanceId),不是维度i-0a1b2c3d4e5f67890)。如果你想只采集特定实例,应该在collection.metrics里用dimensions字段硬编码,而不是在这里过滤。

max_retries: 2是防抖关键。ListMetrics 在高并发下偶尔会返回ThrottlingException,设成 2 次重试能极大提升发现成功率。但别设成 5,否则一次失败发现会拖慢整个启动过程。

3.3 采集配置:定义如何拉取、聚合、暴露指标

collection: sample_interval: 60 metrics: - namespace: "AWS/EC2" metric_name: "CPUUtilization" statistics: ["Average"] period: 300 dimensions: - name: "InstanceId" value: "*" aws_tag_select: - "Name" - "Environment"

这才是真正的“业务逻辑”。我们逐字段看:

  • sample_interval: 60:Exporter 每 60 秒触发一次采集循环。它和 Prometheus 的scrape_interval是独立的,但建议保持一致(比如都设 60s),否则会出现数据点对不齐。
  • statistics: ["Average"]:CloudWatch 支持Average,Sum,Maximum,Minimum,SampleCount五种统计方式。Average最常用,但对计数类指标(如Invocations),你应该用Sum,否则会得到“平均每秒调用次数”,而不是“总调用次数”。
  • period: 300:告诉 CloudWatch “我要查过去 5 分钟的数据”。这个值必须是 CloudWatch 支持的周期(60, 300, 3600, 86400 秒),不能写 120 或 600。
  • dimensions是灵魂所在。value: "*"表示通配所有该维度的值(比如所有 EC2 实例),这是动态发现的基础。但要注意:*通配符只在discovery阶段生效,在collection阶段,它会被替换成实际发现的维度值列表。如果你写value: "i-0a1b2c3d4e5f67890",那就是静态采集,失去了自动化意义。
  • aws_tag_select:启用后,它会调用tag:GetResourcesAPI,根据当前指标关联的资源 ARN(如arn:aws:ec2:us-east-1:123456789012:instance/i-0a1b2c3d4e5f67890)去查标签。["Name", "Environment"]表示只注入这两个标签。实测下来,这个 API 调用比GetMetricStatistics还贵,所以千万别写["*"],那会把所有标签都拉下来,cardinality 直接爆炸。

3.4 高级配置:为生产环境兜底

advanced: collection: max_concurrent_requests: 10 timeout_seconds: 10 discovery: max_concurrent_namespaces: 3 jvm: expose_hotspot_metrics: true
  • max_concurrent_requests: 10:这是并发控制的闸门。CloudWatch 默认每账户每区域 50 QPS,但这是所有 API 的总和。如果你的导出器占了 10 并发,其他服务(比如 Terraform Apply)就只剩 40。设成 10 是平衡点,既能保证采集速度,又留足余量。如果发现cloudwatch_collection_errors_total{error_type="ThrottlingException"}持续上升,就该调低它。
  • timeout_seconds: 10GetMetricStatistics的单次超时。CloudWatch 在数据量大时可能响应慢,设成 10 秒比默认的 30 秒更激进,能更快失败、更快重试,避免线程池被占满。
  • max_concurrent_namespaces: 3:ListMetrics 是跨命名空间并发的,设成 3 意味着最多同时查 3 个命名空间(如AWS/EC2,AWS/RDS,AWS/ELB),避免瞬间打满 CloudWatch 的 ListMetrics 限额(默认 5 QPS)。
  • expose_hotspot_metrics: true:开启 JVM 自身指标。它会自动注册jvm_memory_pool_used_bytes,jvm_threads_current,jvm_gc_pause_seconds等 30+ 个指标。这些指标和 CloudWatch 指标在同一端点暴露,让你一眼看出“是不是 GC 导致了指标采集延迟”。

4. 实操过程与核心环节实现

现在我们来走一遍从零开始,把这个导出器跑起来的完整流程。我会以 Linux/macOS 为例,Windows 用户请自行替换mvnjava命令路径。

4.1 环境准备与构建

首先确认 Java 版本:

java -version # 输出应为:openjdk version "11.0.20" 2023-01-17 # 或 openjdk version "17.0.8" 2023-07-18 # Java 8 也能跑,但强烈建议用 11+,因为 AWS SDK v2 要求 Java 11+

然后克隆仓库(假设你已下载 ZIP 并解压):

cd LFkjCRQ8yVqubXToKTFY-master-9a89cf9d111efdf32df00d30cbe82541a833ab8b ls -la # 你会看到 pom.xml, src/, example.yml, Dockerfile 等

构建 JAR 包(Maven 3.8+):

mvn clean package -DskipTests # -DskipTests 是为了加速,生产环境部署前务必去掉,跑一遍 test # 构建成功后,target/ 目录下会出现 cloudwatch-exporter-1.0.0.jar

实操心得:第一次构建可能会慢,因为要下载 AWS SDK v2、Prometheus Client、SLF4J 等依赖。建议提前执行mvn dependency:go-offline把依赖下全。另外,pom.xml<maven.compiler.source><maven.compiler.target>都设为11,如果你强行用 Java 8 编译,会报Unsupported class file major version 61错误。

4.2 本地运行与验证

在本地运行前,先配置 AWS 凭证。最安全的方式是用~/.aws/credentials

mkdir -p ~/.aws cat > ~/.aws/credentials << 'EOF' [default] aws_access_key_id = YOUR_ACCESS_KEY_ID aws_secret_access_key = YOUR_SECRET_ACCESS_KEY EOF chmod 600 ~/.aws/credentials

然后启动导出器:

java -jar target/cloudwatch-exporter-1.0.0.jar \ --config-file example.yml \ --port 9106 # --config-file 指定 YAML 配置,--port 覆盖 server.port

启动后,你会看到类似日志:

INFO c.c.CloudWatchExporterApplication - Started CloudWatchExporterApplication in 3.212 seconds (JVM running for 3.724) INFO c.c.d.CloudWatchDiscoveryService - Discovered 12 metrics in namespace AWS/EC2 INFO c.c.d.CloudWatchDiscoveryService - Discovered 8 metrics in namespace AWS/RDS

立刻验证:

curl -s http://localhost:9106/metrics | head -20 # 你应该看到: # # HELP cloudwatch_cpu_utilization Average CPU utilization percentage # # TYPE cloudwatch_cpu_utilization gauge # cloudwatch_cpu_utilization{cw_namespace="AWS/EC2",cw_metric_name="CPUUtilization",cw_statistic="Average",cw_period="300",InstanceId="i-0a1b2c3d4e5f67890"} 12.34 # # HELP jvm_memory_used_bytes Used bytes of a given JVM memory area. # # TYPE jvm_memory_used_bytes gauge # jvm_memory_used_bytes{area="heap"} 1.23456789E8

注意:head -20只是快速确认格式,真正要看全量指标,用curl -s http://localhost:9106/metrics | grep "cloudwatch_" | wc -l统计行数。如果返回 0,大概率是example.yml里的namespacesmetric_names写错了,或者你的 AWS 凭证没权限(检查cloudwatch:ListMetrics是否授权)。

4.3 Docker 部署:生产就绪的容器化方案

项目自带Dockerfile,构建镜像只需一行:

docker build -t cloudwatch-exporter:1.0.0 .

Dockerfile的关键点:
- 基础镜像是eclipse-jetty:11-jre11-slim(轻量、安全、Java 11 原生支持)
- 把target/cloudwatch-exporter-1.0.0.jarCOPY 进镜像
- 暴露9106端口
- 启动命令是java -jar /app.jar --config-file /config.yml

运行容器(以 ECS 为例):

docker run -d \ --name cw-exporter \ -p 9106:9106 \ -v $(pwd)/example.yml:/config.yml:ro \ -e AWS_REGION=us-east-1 \ -e AWS_ACCESS_KEY_ID=YOUR_KEY \ -e AWS_SECRET_ACCESS_KEY=YOUR_SECRET \ cloudwatch-exporter:1.0.0

但在生产环境,绝不要用-e AWS_ACCESS_KEY_ID!正确姿势是:

  • EC2 实例:给实例配置 IAM Role,容器内自动继承凭证;
  • ECS Fargate:在 Task Definition 的taskRoleArn字段指定 Role;
  • EKS Pod:用 IRSA,ServiceAccount 注解eks.amazonaws.com/role-arn

验证容器内指标:

docker exec cw-exporter curl -s http://localhost:9106/health # 返回 {"status":"UP","cloudwatch":"UP"}

4.4 Prometheus 集成:让它真正工作起来

在 Prometheus 的prometheus.yml中添加 job:

scrape_configs: - job_name: 'cloudwatch-exporter' static_configs: - targets: ['cloudwatch-exporter:9106'] # 如果和 Prometheus 同 Docker 网络 # 或 targets: ['host.docker.internal:9106'] # macOS/Windows Docker Desktop # 或 targets: ['192.168.1.100:9106'] # Linux 直接 IP metrics_path: '/metrics' scheme: http scrape_interval: 60s scrape_timeout: 30s

重启 Prometheus 后,访问http://<prometheus>/targets,你应该看到这个 job 是UP状态,并且Labels列显示instance="cloudwatch-exporter:9106"

最后,写一条 PromQL 验证:

avg by (InstanceId) (cloudwatch_cpu_utilization{cw_namespace="AWS/EC2", cw_metric_name="CPUUtilization"})

如果返回了多个InstanceId的平均值,恭喜,你已经打通了 AWS 监控数据到 Prometheus 的最后一公里。

5. 常见问题与排查技巧实录

在真实生产环境中,这个导出器最常见的问题不是“跑不起来”,而是“跑得不稳”或“数据不准”。我把过去两年帮客户排障的经验,浓缩成一张速查表,并附上独家调试技巧。

5.1 常见问题速查表

问题现象可能原因快速验证命令解决方案
/metrics返回空,或只有 JVM 指标,没有cloudwatch_开头的指标discovery阶段失败,没发现任何指标curl -s http://localhost:9106/actuator/prometheus | grep cloudwatch_discovery_total检查example.ymlnamespacesmetric_names是否拼写正确;检查 AWS 凭证是否有cloudwatch:ListMetrics权限;查看日志中Discovered X metrics行数
/metrics有指标,但数值一直是0NaNcollection阶段GetMetricStatistics返回空数据点curl -s "http://localhost:9106/actuator/prometheus" | grep cloudwatch_collection_duration_seconds_count检查period是否超出 CloudWatch 数据保留期(EC2 默认 15 个月,但免费层只存 2 周);检查dimensionsvalue是否匹配真实资源 ID;用 AWS CLI 手动验证:aws cloudwatch get-metric-statistics --namespace AWS/EC2 --metric-name CPUUtilization --dimensions Name=InstanceId,Value=i-xxx --start-time $(date -d '5 minutes ago' +%s) --end-time $(date +%s) --period 300 --statistics Average --output json
Prometheus 抓取失败,状态为DOWN导出器 HTTP Server 崩溃,或网络不通curl -I http://localhost:9106/health(应返回 200);netstat -tuln \| grep 9106检查导出器日志是否有OutOfMemoryError;增加 JVM 参数:java -Xms512m -Xmx1024m -jar ...;检查防火墙是否放行 9106 端口
指标标签过多,Prometheus 存储暴涨aws_tag_select启用了["*"],或维度值本身含高基数字段(如RequestIdcurl -s http://localhost:9106/metrics \| grep "cloudwatch_cpu_utilization{" \| head -1立即修改example.yml,将aws_tag_select改为明确的["Name", "Environment"];在dimensions中避免使用RequestIdTraceId等唯一 ID 类维度
导出器 CPU 使用率持续 100%discoverycollection并发过高,或 GC 频繁jstat -gc <pid>curl -s http://localhost:9106/actuator/prometheus \| grep jvm_gc_降低advanced.collection.max_concurrent_requests至 5;增加jvm.expose_hotspot_metrics: true后,用 Grafana 看jvm_gc_pause_seconds_max是否异常;考虑升级到 Java 17,ZGC 更友好

5.2 独家调试技巧:三步定位根因

第一步:打开 DEBUG 日志

默认日志级别是 INFO,看不到详细请求。启动时加参数:

java -Dlogging.level.com.cloudwatch=DEBUG -jar target/cloudwatch-exporter-1.0.0.jar --config-file example.yml

你会看到类似:

DEBUG c.c.c.CloudWatchCollectionService - Sending GetMetricStatisticsRequest for metric: AWS/EC2/CPUUtilization, dimensions: [{name=InstanceId, value=i-0a1b2c3d4e5f67890}] DEBUG c.c.c.CloudWatchCollectionService - GetMetricStatisticsResponse received, data points count: 12

这能直接确认:请求发出去了没?CloudWatch 返回数据了没?数据点数量对不对?

第二步:用/debug/metrics端点看内部状态

这个端点不在公开文档里,但代码里埋了(DebugMetricsEndpoint类)。访问:

curl -s http://localhost:9106/debug/metrics

返回 JSON,包含:

  • discovery_cache_size: 当前缓存的指标数
  • collection_queue_size: 待采集的指标队列长度
  • http_client_active_requests: 当前活跃的 HTTP 请求连接数
  • jvm_memory_committed_bytes: JVM 已提交内存

如果collection_queue_size持续 > 100,说明采集跟不上发现速度,要调大max_concurrent_requests或调小sample_interval

第三步:用 JMX 深度诊断(Java 11+)

导出器集成了io.prometheus:simpleclient_hotspot,它会自动暴露 JMX Bean。用jconsole连接:

jconsole localhost:9106

MBeans标签页,展开java.langMemoryAttributes,看HeapMemoryUsage.used;展开com.cloudwatchCollectorAttributes,看LastCollectionTime(毫秒时间戳),计算距今多久,就能判断采集是否卡住。

实操心得:我在给一家游戏公司做支持时,遇到过一个诡异问题:导出器内存稳定在 800MB,但jvm_memory_used_bytes{area="heap"}却显示 2GB。最后发现是simpleclient_hotspotBufferPoolMXBean采集逻辑有 bug,它把 Direct Buffer 内存也算进了 heap。解决方案是关掉expose_hotspot_metrics: false,改用jvm_direct_*指标单独监控。这个坑,只有真刀真枪跑过一周以上才能踩到。

6. 二次开发与定制化扩展

这个项目之所以被称作“适合二次定制开发”,是因为它的源码结构清晰,模块职责单一,且预留了大量 Hook 点。我来分享三个最实用的定制场景,以及对应的代码修改路径。

6.1 场景一:添加自定义指标处理器(比如把 NetworkIn 字节转为 Mbps)

CloudWatch 的NetworkIn单位是字节,Prometheus 里直接暴露cloudwatch_network_in_bytes不够直观。你想暴露成cloudwatch_network_in_mbps,公式是bytes * 8 / 1000 / 1000 / period

修改步骤:
1. 在src/main/java/com/cloudwatch/collector/下新建NetworkInMbpsProcessor.java
2. 实现MetricProcessor接口,重写process方法:
java public class NetworkInMbpsProcessor implements MetricProcessor { @Override public void process(MetricFamilySamples.Sample sample, double value) { // value 是原始字节数,sample.labelValues 包含维度 double mbps = value * 8 / 1_000_000 / 300; // 除以 period=300 秒 sample.value = mbps; sample.name = "cloudwatch_network_in_mbps"; // 覆盖指标名 } }
3. 在CloudWatchCollectionServicecollectMetrics方法里,对metricName.equals("NetworkIn")的指标,插入这个 processor。

这样,你不用改任何配置,只要在example.yml里把metric_name: "NetworkIn"的项加上processor: "network_in_mbps",就能自动生效。

6.2 场景二:对接企业内部 CMDB,用主机名替换 InstanceId

很多企业 CMDB 里,InstanceId=i-0a1b2c3d4e5f67890对应的主机名是web-prod-01。你想把 Prometheus 标签里的InstanceId="i-0a1b2c3d4e5f67890"替换为hostname="web-prod-01"

修改步骤:
1. 在src/main/java/com/cloudwatch/dimension/下新建CmdbDimensionResolver.java
2. 实现DimensionResolver接口,提供resolve(String instanceId)方法,内部调用 CMDB REST API;
3. 在CloudWatchDiscoveryServicediscoverMetrics方法里,对dimension.name == "InstanceId"的维度值,调用cmdbResolver.resolve(value)获取主机名,并存入MetricDefinition.dimensions

这样,example.yml里只需加一行dimension_resolver: "cmdb",所有InstanceId标签就自动变成可读的主机名。

6.3 场景三:添加告警指标(比如 CloudWatch API 调用失败率 > 5%)

你想在 Grafana 里画一张图,显示“过去 5 分钟 CloudWatch API 失败率”,并设置告警。

修改步骤:
1. 在src/main/java/com/cloudwatch/metrics/下新建CloudWatchApiErrorRateCollector.java
2. 继承Collector类,重写collect方法,从cloudwatch_collection_errors_totalcloudwatch_collection_total计数器中计算比率;
3. 在CloudWatchExporterApplicationmain方法里,调用collectorRegistry.register(new CloudWatchApiErrorRateCollector())

最终,/metrics端点就会多出:

# HELP cloudwatch_api_error_rate CloudWatch API error rate in last 5 minutes # TYPE cloudwatch_api_error_rate gauge cloudwatch_api_error_rate 0.023

然后你就可以在 Prometheus 里写告警规则:

- alert: CloudWatchAPIFailureRateHigh expr: cloudwatch_api_error_rate > 0.05 for: 5m labels: severity: warning annotations: summary: "CloudWatch API failure rate is high"

我个人在实际操作中的体会是:这个导出器最大的价值,不是它“现在能做什么”,而是它“将来能很容易地做什么”。它的 Maven 模块划分(core,collector,discovery,http)让每个功能都像乐高积木一样可插拔。我见过最猛的定制,是把整个discovery模块替换成从 Datadog API 拉取指标定义,从而实现了 CloudWatch + Datadog 双源指标统一暴露。只要你理解了它的三层抽象(发现→采集→暴露),任何监控数据源的接入,本质上都是在discovery模块里写一个新的XXXDiscoveryService,在collector模块里写一个新的XXXCollector,然后注册进去——就这么简单。

本文还有配套的精品资源,点击获取

简介:一个用Java开发的轻量级工具,能把AWS CloudWatch里的各种监控指标(比如EC2 CPU使用率、RDS连接数、Lambda调用次数等)实时转换成Prometheus能直接抓取的格式。支持Java 8+,编译用mvn package,运行就一条java -jar命令,指定端口和YAML配置文件(自带example.yml参考),默认走9106端口。认证走AWS标准凭证链:环境变量(AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY)、IAM角色、~/.aws/credentials文件都行;最小权限只要cloudwatch:ListMetrics和cloudwatch:GetMetricStatistics,如果要用标签筛选功能(aws_tag_select),再加个tag:GetResources就行。配置全靠YAML,可以按命名空间、指标名、维度(如InstanceId、LoadBalancerName)、统计方式(Average/Sum/Maximum)、采样周期(比如5分钟)和抓取间隔灵活设置。带Dockerfile,build完就能docker run跑起来;项目结构规范,含完整Maven配置(pom.xml)、源码(src/main)、测试(src/test)、许可证(LICENSE)、说明文档(README.md)、贡献指南(CONTRIBUTING.md)和安全策略(SECURITY.md),适合嵌入现有Prometheus监控体系,也方便二次开发或定制指标采集逻辑。


本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/9 9:05:26

别再到处找安装包了!手把手教你从官网下载并配置IDEA 2021.3.2社区版(附学生认证白嫖激活码方法)

从零开始安全获取IDEA&#xff1a;官网下载、配置优化与学生认证全指南 第一次打开JetBrains官网时&#xff0c;我被满屏的英文和专业术语吓到了——哪个才是真正的下载按钮&#xff1f;Community版和Ultimate版有什么区别&#xff1f;为什么同学能用高级功能而我只能看到灰色…

作者头像 李华
网站建设 2026/6/9 9:02:19

医院HIS药房模块实战避坑系列》之三:公立/私立医院药品调价模式对比:账务处理与行业演进

标签:药品调价、零差率、公立医院、私立医院、HIS账务、采购结算 作者:蓝鸟1974、豆包 摘要:药品价格调整是HIS药房进销存模块的高频核心业务。受集采政策、药品零差率、采购渠道差异影响,公立医院与私立医院在调价流程、价格规则、结算方式、账务处理上存在显著区别,也…

作者头像 李华