Micrometer 系列【4】入门案例
文章目录
- 前言
- 1. 环境准备
- 1.1 开发环境
- 1.2 依赖配置
- 2 基础知识:埋点
- 2.1 核心定义
- 2.2 分类(多角度)
- 2.3 手动埋点:定制业务逻辑监控
- 2.3.1 编程式
- 2.3.2 注解式
- 2.4 自动埋点:开箱即用
- 3. 代码实现
- 3.1 创建注册表
- 3.2 自动埋点
- 3.3 手动埋点
- 3.4 输出指标
- 3.5 输出示例
前言
需求说明:
- 不依赖
Spring/SpringBoot; - 不接入
Prometheus、Grafana等监控服务; - 仅使用
Micrometer核心包,内存内完成埋点、读取指标; - 死循环模拟业务操作(生产/消费任务),同时绑定
JVM内置指标; - 采用内置
SimpleMeterRegistry(内存注册表,仅本地存储,不对外推送)。
1. 环境准备
1.1 开发环境
| 工具 | 版本 |
|---|---|
| JDK | 17+ |
| Maven | 3.6+ |
| Micrometer | 1.17.0 |
本示例仅依赖
micrometer-core,无需Spring、Prometheus或任何外部服务。
1.2 依赖配置
创建一个普通Maven工程,只引入micrometer核心基础包,无任何web、监控导出依赖:
<dependencyManagement><dependencies><dependency><groupId>io.micrometer</groupId><artifactId>micrometer-bom</artifactId><version>1.17.0</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>io.micrometer</groupId><artifactId>micrometer-core</artifactId></dependency></dependencies>micrometer-core包:
micrometer-core严格遵循最小依赖:
编译期仅依赖
JSpecify,无其他第三方强制依赖;停顿检测功能需要运行时依赖
LatencyUtils(默认关闭,故不默认引入);客户端百分位统计需要运行时依赖
HdrHistogram,不用可直接排除。
2 基础知识:埋点
2.1 核心定义
可观测性中的埋点(英文通常叫instrumentation或telemetry/data collection),是指在系统内植入采集逻辑,收集指标、日志、链路追踪等遥测数据,以此还原系统内部运行状态的技术手段,是可观测性体系数据采集的核心基础。
简单来说,就是提前在系统各处布下 “信息采集传感器”,传感器持续产出指标、调用链路、运行日志三类信息。有了这些数据,我们才能看清分布式系统内部运行状况,做到快速排查故障、分析性能问题。
典型应用场景:
- 用户行为分析:页面访问、按钮点击、转化漏斗
- 性能监控:接口耗时(
RT)、QPS、错误率 - 业务指标:订单量、支付金额、活跃用户数
- 故障排查:异常堆栈、链路追踪
2.2 分类(多角度)
按实现方式可分为:
| 类型 | 做法 | 特点 |
|---|---|---|
| 自动埋点 | SDK 自动采集通用信号 | 省事,覆盖不到业务细节 |
| 手动埋点 | 手写采集代码 | 精准,但要维护 |
| 混合埋点 | 两者结合 | 最优实践 |
按数据类型可分为:
| 类型 | 采什么 | 例子 | 比喻 |
|---|---|---|---|
| 指标埋点 | 数值统计(数量、耗时) | “今天订单量 1000、平均耗时 200ms” | 仪表盘 |
| 日志埋点 | 事件明细记录 | “用户 A 在 10:23 登录失败” | 行车记录仪 |
| 链路埋点 | 调用路径追踪 | “请求→网关→订单服务→支付服务” | 导航轨迹 |
按位置层次可分为:
| 类型 | 位置 | 典型采集内容 |
|---|---|---|
| 前端埋点 | 浏览器 / H5 | 页面浏览、点击、停留时长、转化漏斗 |
| 移动端埋点 | App / 小程序 | 启动、崩溃、手势、页面跳转 |
| 后端埋点 | 服务端 | 接口耗时、QPS、错误率、依赖调用 |
| 基础设施埋点 | 主机 / 容器 / 中间件 | CPU、内存、磁盘、网络、JVM、DB 连接池 |
按业务目的可分为:
| 类型 | 目的 | 典型场景 |
|---|---|---|
| 业务埋点 | 分析用户行为、衡量业务效果 | 注册转化、下单漏斗、功能使用率 |
| 性能埋点 | 监控系统性能表现 | 接口 RT、页面加载时间、慢查询 |
| 异常埋点 | 捕获和追踪错误 | 接口报错、崩溃、超时、限流触发 |
| 安全埋点 | 审计合规、风控 | 登录尝试、越权操作、敏感数据访问 |
按指标类型可分为:
| 类型 | 采集形态 | 例子 | 对应 API |
|---|---|---|---|
| 计数型 | 只增不减的累计值 | 订单总数、错误次数 | Counter |
| 计时型 | 事件的耗时分布 | 接口响应时间 | Timer |
| 分布型 | 数据的分布情况 | 一批请求耗时的 P99 | DistributionSummary |
| 状态型 | 瞬时高低起伏的值 | 当前连接数、内存占用 | Gauge |
| 增量型 | 可增可减的累计值 | 队列长度、积分余额 | Gauge(或自增自减) |
按采集时机可分为:
| 类型 | 触发方式 | 例子 |
|---|---|---|
| 事件触发型 | 用户/系统动作发生时记录 | 点击按钮、发起请求 |
| 状态变更型 | 某个状态发生变化时记录 | 订单状态从"待支付"变"已支付" |
| 周期采集型 | 定时轮询采集当前状态 | 每隔 10 秒抓一次 CPU 使用率 |
| 异常触发型 | 发生异常时才记录 | 接口超时、抛异常时上报堆栈 |
同一个埋点,换个角度就属于不同类别:
比如"记录订单接口的耗时"├── 角度一(怎么埋):手动埋点 ├── 角度二(埋什么):指标埋点 ├── 角度三(埋在哪):后端埋点 ├── 角度四(为什么):性能埋点 ├── 角度五(什么形态):计时型 └── 角度六(什么时候):事件触发型2.3 手动埋点:定制业务逻辑监控
当需要监控特定业务流程(如订单支付成功率、特定方法耗时)时,自动埋点无法满足需求,此时需通过代码进行手动埋点。
手动埋点又分两类:编程式(直接调API)和注解式(AOP拦截)。
2.3.1 编程式
直接用MeterRegistry创建和调用,核心分为三步获取→创建→调用。
简单示例:
// 1. 获取 MeterRegistry(通常通过依赖注入)@AutowiredprivateMeterRegistryregistry;// 2. 创建或获取 meter(同名同 tag 只创建一次)Counterorders=registry.counter("orders.created","channel","app");TimerapiTimer=registry.timer("api.request.duration","endpoint","/order");// 3. 在关键位置"埋点"调用orders.increment();// Counter: +1apiTimer.record(()->doSomething());// Timer: 自动统计耗时registry.gauge("queue.size",queue,Queue::size);// Gauge: 实时反映队列大小或者用全局静态入口:
Metrics.counter("orders.created").increment();2.3.2 注解式
使用@Timed等注解标记在方法或类上,Micrometer会通过AOP自动拦截并记录该方法执行耗时。这种方式代码侵入性小,适合快速统计接口性能,但无法像编程式那样灵活记录业务状态。
使用示例:
@Timed(value="order.create",extraTags={"service","order"},percentiles={0.5,0.95})publicOrdercreateOrder(@MeterTag(key="userType")StringuserId){// 方法执行时间自动记录到 Timer// 异常时自动打 exception="XxxException" 标签returndoCreate(userId);}@Counted(value="payment.count",recordFailuresOnly=true)publicvoidpay(StringorderId){// 仅失败时计数,标签 result="failure" + exception="..."doPay(orderId);}2.4 自动埋点:开箱即用
Micrometer提供了多种框架、中间件、数据库等指标集成,会自动采集大量基础指标,无需编写额外代码。这种方式适用于监控JVM状态、HTTP请求、数据库连接池等通用场景。
在Spring Boot使用场景中,只需引入spring-boot-starter-actuator和对应的注册表依赖(如micrometer-registry-prometheus),并通过配置management.endpoints.web.exposure.include暴露监控端点即可 。
如果只使用Micrometer核心依赖,需要手动创建注册表,并绑定指标器,即可自动实现埋点采集指标。
3. 代码实现
完整代码如下:
publicclassJvmMetricsDemoApplication{publicstaticvoidmain(String[]args)throwsInterruptedException{SimpleMeterRegistryregistry=newSimpleMeterRegistry();Metrics.addRegistry(registry);// ========== 1. 注册 JVM 指标 ==========newJvmMemoryMetrics().bindTo(registry);newJvmGcMetrics().bindTo(registry);newJvmThreadMetrics().bindTo(registry);newProcessorMetrics().bindTo(registry);// ========== 2. 业务对象 + 业务指标 ==========OrderServiceorderService=newOrderService();TimerorderTimer=registry.timer("biz.order.create");// ========== 3. 死循环调用业务 ==========System.out.println("=== JVM + 业务指标监控 (每5秒输出一次,Ctrl+C 停止) ===\n");longlastPrint=System.currentTimeMillis();while(true){// 调用业务方法,自动记录耗时orderTimer.record(orderService::createOrder);// 每 5 秒输出全部指标longnow=System.currentTimeMillis();if(now-lastPrint>=5000){lastPrint=now;printAllMetrics(registry);}TimeUnit.MILLISECONDS.sleep(200);}}privatestaticvoidprintAllMetrics(SimpleMeterRegistryregistry){System.out.println("───── "+LocalTime.now()+" ─────");registry.getMeters().forEach(meter->meter.measure().forEach(ms->{if(ms.getValue()>0){System.out.printf("%-45s %-15s %.2f%n",meter.getId().getName(),ms.getStatistic(),ms.getValue());}}));System.out.println();}/** * 业务对象 */staticclassOrderService{voidcreateOrder(){try{// 模拟业务逻辑耗时 20~100msTimeUnit.MILLISECONDS.sleep((long)(Math.random()*80+20));}catch(InterruptedExceptione){Thread.currentThread().interrupt();}}}}3.1 创建注册表
SimpleMeterRegistry是Micrometer内置的内存级注册表,所有指标数据仅保存在JVM堆内存中,不对外推送、不落盘,适合开发调试和单元测试场景。
// SimpleMeterRegistry:内存注册表,不依赖任何外部监控系统MeterRegistryregistry=newSimpleMeterRegistry();通常,一个应用只有一个
MeterRegistry入口,可以使用CompositeMeterRegistry包含多个子Registry,实现一次埋点、到处上报。
3.2 自动埋点
使用bindTo(registry)将JVM内部状态(内存、GC、线程、CPU)自动绑定为Micrometer指标,之后每次读取时实时获取当前值,无需手动采样。
// 绑定 JVM 内置指标newJvmMemoryMetrics().bindTo(registry);// 堆内存、非堆内存newJvmGcMetrics().bindTo(registry);// GC 次数、耗时newJvmThreadMetrics().bindTo(registry);// 线程数、状态分布newProcessorMetrics().bindTo(registry);// CPU 使用率、CPU 时间3.3 手动埋点
业务对象是一个简单的内部类,createOrder()模拟20~100ms的业务处理耗时:
staticclassOrderService{voidcreateOrder(){// 模拟业务逻辑耗时 20~100msTimeUnit.MILLISECONDS.sleep((long)(Math.random()*80+20));}}创建一个普通业务对象,再用Timer来统计其方法调用耗时。Timer是最常用的业务埋点方式。
// 创建业务对象OrderServiceorderService=newOrderService();// 注册 Timer,统计下单耗时TimerorderTimer=registry.timer("biz.order.create");死循环调用业务,Timer自动记录每次调用的耗时。
longlastPrint=System.currentTimeMillis();while(true){// 调用业务方法,Timer 自动记录耗时orderTimer.record(orderService::createOrder);// 每 5 秒输出全部指标longnow=System.currentTimeMillis();if(now-lastPrint>=5000){lastPrint=now;printAllMetrics(registry);}TimeUnit.MILLISECONDS.sleep(200);// 避免 CPU 空转}3.4 输出指标
遍历注册表中所有Meter,打印每个测量值。过滤零值以减少输出噪音。
privatestaticvoidprintAllMetrics(SimpleMeterRegistryregistry){System.out.println("───── "+LocalTime.now()+" ─────");registry.getMeters().forEach(meter->meter.measure().forEach(ms->{if(ms.getValue()>0){System.out.printf("%-45s %-15s %.2f%n",meter.getId().getName(),ms.getStatistic(),ms.getValue());}}));System.out.println();}3.5 输出示例
───── 13:51:45 ───── jvm.memory.used VALUE 16800000.00 ← 堆内存已用 jvm.memory.max VALUE 1073741824.00 ← 堆内存上限 jvm.memory.committed VALUE 163577856.00 ← 堆内存已提交 jvm.threads.live VALUE 11.00 ← 活跃线程 jvm.threads.daemon VALUE 9.00 ← 守护线程 jvm.threads.peak VALUE 11.00 ← 线程峰值 jvm.threads.states VALUE 5.00 ← RUNNABLE 线程 jvm.threads.started COUNT 11.00 ← 累计启动线程 jvm.gc.max.data.size VALUE 4255121408.00 ← GC 数据区上限 process.cpu.usage VALUE 0.08 ← CPU 使用率 process.cpu.time COUNT 4062500000.00 ← CPU 时间(ns) system.cpu.count VALUE 12.00 ← CPU 核心数 biz.order.create COUNT 25.00 ← 下单调用次数 biz.order.create TOTAL_TIME 1.35 ← 下单总耗时(秒) biz.order.create MAX 0.09 ← 下单最大耗时(秒)