禅与计算机 禅与计算机
首页
  • Java基础

    • 聊一聊java一些核心知识点
    • 聊聊java面向对象核心知识点
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 写作

    • 写好技术博客的5大核心原则:从认知科学到AI工具的全流程指南
  • 开发工具

    • IDEA配置详解与高效使用指南
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • 从Lucene到Elasticsearch:进化之路
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

sharkchili

计算机禅修者
首页
  • Java基础

    • 聊一聊java一些核心知识点
    • 聊聊java面向对象核心知识点
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 写作

    • 写好技术博客的5大核心原则:从认知科学到AI工具的全流程指南
  • 开发工具

    • IDEA配置详解与高效使用指南
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • 从Lucene到Elasticsearch:进化之路
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 项目编排

  • 场景设计

  • CI-CD

  • 架构设计

  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
      • 写在文章开头
      • 详解监控系统核心概念
        • 纬度性
        • 速率聚合
        • 发布
      • 详解Micrometer中常见的计量器
        • 计时器
        • 计数器
        • 仪表
        • 分布式摘要
      • 详解micrometer高级特性
        • Meter Filters过滤
      • 小结
      • 参考
    • 从micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • 软件工程实践
  • 监控方法论
sharkchili
2026-03-25
目录

Java监控度量Micrometer全解析

# 写在文章开头

之前的一篇文章中笔者介绍了应用埋点监控的主流方案,这其中涉及到应用指标计量器,由于计量器涉及的概念比较多,所以笔者才考虑抽出这篇文章专门来介绍一下java监控度量库Micrometer。Micrometer作为最流行的客观测系统简单门面,使得Java开发能够基于jvm的应用进行仪表化,无需受限于特定的提供商,即无缝衔接主流观测系统例如elastic、Prometheus、grafana等。

通过本文的阅读,您将会对Micrometer维度模型及常见计量器guage、counter、timer以及Distribution Summary有着深刻的理解和掌握,让你编写符合自己需求的应用监控指标且能够跨维度进行深入分析。

我是 SharkChili ,Java 开发者,Java Guide 开源项目维护者。欢迎关注我的公众号:写代码的SharkChili,也欢迎您了解我的开源项目 mini-redis:https://github.com/shark-ctrl/mini-redis (opens new window)。

为方便与读者交流,现已创建读者群。关注上方公众号获取我的联系方式,添加时备注加群即可加入。

# 详解监控系统核心概念

# 纬度性

维度性是Micrometer中最显著的一个优势,相比于其他的计量器,它可以针对应用指标通过标签键值对来丰富指标,可以让prometheus此类观测系统能够针对指标进行维度化区分,例如针对http请求数的计量器(后文会展开详细介绍),就可以通过请求的接口映射构成一个维度进行区分:

这里笔者也贴出从spring actual上找到的自定义的http_requests_total来印证这一点(后文会介绍counter指标),可以看到针对相同的指标http_requests_total笔者针对不同的http接口映射名称打上了不同的标签来层次化区分:

# HELP http_requests_total  
# TYPE http_requests_total counter
http_requests_total{application="web-service",uri="/function",} 0.0
http_requests_total{application="web-service",uri="/hello",} 0.0

1
2
3
4
5

# 速率聚合

大部分应用进行监控时时候,更多是观察应用的趋势和变化,所以针对某个时间段的样本指进行聚合。这其中就涉及服务端聚合和客户端聚合两种情况。 先来说说服务端速率聚合,这种方式本质上就是通过Prometheus轮询或者抓取期间抓取的数据,通过服务端自带的表达式例如grafana等promql针对从过去到现在的应用指标进行收敛计算,按照micrometer官网的说法,它要求无论是告警监控还是金丝雀发布涉及到的指标分析,我们都应该以速率聚合数据位基础进行自动化,这也就是为什么本文针对micrometer计量指标进行介绍时会在一些带有时间特性的分布式摘要计量指标进行重点说明的原因所在:

然后就是客户端聚合,即符合:

  1. 针对单位时间内的数据进行聚合,并通过少量的数学计算满足查询而非通过绝对值进行分析
  2. 若几乎或者没有数学操作,则通过发布预聚合构建有意义的数据指标

我们还是以micrometer为例,它再进行客户端速率时用到一个步进值来维护速率数据,例如:我们假设现在有个计量数据需要统计变化的速率,按照每10s进行轮询发布,如果micrometer检测到当前间隔已过,则会移动到先前一个间隔窗口获取数据进行收敛聚合,如下图:

  1. 第1个10s的窗口进行速率聚合时,因为没有上1个窗口,所以没有值,poll函数拉取到的聚合指标是0增长
  2. 第2个窗口进行速率聚合,获取上一个间隔窗口,算得两个数据,由此可知速率0.2/s的增长量
  3. 第3个窗口同理算得速率为0.3/s的增长量

基于这种步进值的算法,若micrometer的计时器以10s作为间隔进行数据发布到监控系统,同时我们观察到有20个请求,每个请求耗时为100ms,就可以得出第一个间隔的请求总数和总耗时:

平均间隔请求总数 count = 间隔时间 * 平均每秒请求数 
								  = 10 * (20/10) = 20

请求总耗时 totalTime = 间隔时间 * 请求总耗时/请求间隔
										= 10 *  (20 * 100 /10 )
										= 2s
1
2
3
4
5
6

注意:可能因为数据原因,读者觉得这种计算方式繁琐啰嗦,实际上micrometer平均数据计算聚合都是通过窗口时间 * 平均速率得出,同时基于micrometer这些计量数据,我们还可以得出每个请求的平均耗时:

totalTime/count= 2s / 20 = 100 ms
1

其他监控系统可以基于分布式摘要得到的总数和平均值推算出:

  1. count为20,对应1分钟也就是6个间隔的请求总数为 6 * 20 = 120
  2. 基于totalTime为2s,也就是1分钟的时延为12s,由此得出平均时延为 12/ 120 = 100ms

# 发布

关于指标的发布主流的是有两种方式:

  1. 客户端主动推动:AppOptics, Atlas, Azure Monitor, Datadog, Dynatrace, Elastic, Graphite, Ganglia, Humio, Influx, JMX, Kairos, New Relic, SignalFx, Wavefront
  2. 服务端轮询拉取:Prometheus, 所有 StatsD 变种

# 详解Micrometer中常见的计量器

# 计时器

介绍完了监控系统的基本概念之后,我们就来介绍一些Micrometer中一些比较常见的度量器,在此之前我们需要先引入micrometer的核心依赖包:

<!--暴露spring监控指标-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
            <version>2.4.1</version>
        </dependency>

        <!--用于导出prometheus系统类型的指标数据-->
        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-registry-prometheus</artifactId>
            <version>1.1.4</version>
        </dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13

配置文件也将指标暴露,方便我们通过http://127.0.0.1:18080/actuator/prometheus调测理解这些指标:

# 暴露并开启所有的端点,Spring Boot Actuator会自动配置一个 URL 为 /actuator/Prometheus 的 HTTP 服务来供 Prometheus 抓取数据
management.endpoints.web.exposure.include=*
# 展示所有的健康信息
management.endpoint.health.show-details=always
# 默认/actuator/Prometheus,添加这个tag方便区分不同的工程
management.metrics.tags.application=${spring.application.name}
# Actuator 监控端点独立端口设置为 18080(与主应用端口分离)
management.server.port=18080
# Actuator 端点的根路径改为 /management(默认是 /actuator)
#management.endpoints.web.base-path=/management
1
2
3
4
5
6
7
8
9
10

先来说说计时器timers,该度量器适用于对于应用时间指标的采集与分析,即短时延及其事件发生的频率,通过该计量器我们只需简单将采集到的时间指标交给timer,timer底层的数据结构和算法就会帮我得出总时间和计数的时间序列,并推算出采集数据的:

  1. 最大耗时
  2. 百分位
  3. 直方图

这里以java开发常用的spring框架为例演示一下timers最经典的用法——http接口请求时延图表,例如:笔者现在有一个hello映射的接口,我们希望通过timer计时器来获取针对接口时延,对应的服务器请求时延指标的开发步骤为:

  1. 全局register注册一个timer计时器
  2. 计时器打上接口映射作为URL标签,实现维度化
  3. 接口请求结束,告知timer本次请求耗时,由timer进行各种时延总数、平均数的计算:

对应的代码如下,首先controller注入注册器并声明timer计时器:

 @Autowired
    private MeterRegistry registry;
    private Timer reqHelloTimer;
1
2
3

随后通过PostConstruct将计时器初始化,并指明可被理解的名称同时打上URL标签指定接口名称,最后再声明要发布的百分位图:

@PostConstruct
    private void init() {
        //名称设置为http.timer,标签设置为uri为/hello,选用合适的名称辅助开发推断理解
        reqHelloTimer = Timer
                .builder("http.timer")
                .publishPercentiles(0.5, 0.95) //发布百分位数区间
                .description("接口请求耗时统计") // 指标的描述
                .tags("uri", "/hello") // url标签指明为hello
                .register(registry);

       
    }
1
2
3
4
5
6
7
8
9
10
11
12

此时我们就可以使用其record方法记录本次耗时并指明耗时单位避免数据指标存在二义性混淆,此时计时器主动获取根据采集到数据维护时间序列:

@GetMapping("/hello")
    public String helloWorld() {
      		  long begin = System.currentTimeMillis();
        int sleepTime = RandomUtil.randomInt(1000);
        log.info("休眠 {} 毫秒", sleepTime);
        ThreadUtil.sleep(sleepTime);
				//记录休眠时间,指明单位为毫秒
        reqHelloTimer.record(System.currentTimeMillis() - begin, TimeUnit.MILLISECONDS);
         int sleepTime = RandomUtil.randomInt(1000);
        log.info("休眠 {} 毫秒", sleepTime);
        ThreadUtil.sleep(sleepTime);
        //记录耗时
        reqHelloTimer.record(System.currentTimeMillis() - begin, TimeUnit.MILLISECONDS);
        //获取请求总数
        long count = reqHelloTimer.count();
        //获取最大耗时
        double max = reqHelloTimer.max(TimeUnit.MILLISECONDS);
        //获取最小耗时
        double totalTime = reqHelloTimer.totalTime(TimeUnit.MILLISECONDS);
        //获取平均耗时
        double mean = reqHelloTimer.mean(TimeUnit.MILLISECONDS);
        log.info("请求次数:{},最大值:{},总耗时:{},平均耗时:{}", count, max, totalTime, mean);

        return "Hello, World!";
    }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

对应的我们试着去请求两次该接口,即可看到timer计量器针对时间所得出的应用指标信息数据:

2025-10-18 01:01:48.057  INFO 57066 --- [nio-8080-exec-1] c.sharkchili.controller.TestController   : 用户请求了 /hello 接口
2025-10-18 01:01:48.057  INFO 57066 --- [nio-8080-exec-1] c.sharkchili.controller.TestController   : 休眠 10 毫秒
2025-10-18 01:01:48.073  INFO 57066 --- [nio-8080-exec-1] c.sharkchili.controller.TestController   : 请求次数:1,最大值:13.0,总耗时:13.0,平均耗时:13.0
2025-10-18 01:16:15.549  INFO 57066 --- [nio-8080-exec-2] c.sharkchili.controller.TestController   : 用户请求了 /hello 接口
2025-10-18 01:16:15.549  INFO 57066 --- [nio-8080-exec-2] c.sharkchili.controller.TestController   : 休眠 335 毫秒
2025-10-18 01:16:15.890  INFO 57066 --- [nio-8080-exec-2] c.sharkchili.controller.TestController   : 请求次数:2,最大值:0.0,总耗时:353.0,平均耗时:176.5
1
2
3
4
5
6

对应的这些指标也会在spring actual上发布显示,可以看到,按照prometheus风格,我们的http.timer被转换为http_timer并拼接上采集数据计量指标名渲染输出。 这里笔者需要着重说明一个quantile这就是我们上文中指定百分位的作用,通过这个分布图我们可以知晓有50%的请求耗时小于0.276824064以及95%的请求耗时小于0.46137344:

# HELP http_timer_seconds 接口请求耗时统计
# TYPE http_timer_seconds summary
http_timer_seconds{application="web-service",uri="/hello",quantile="0.5",} 0.276824064
http_timer_seconds{application="web-service",uri="/hello",quantile="0.95",} 0.46137344
http_timer_seconds_count{application="web-service",uri="/hello",} 3.0
http_timer_seconds_sum{application="web-service",uri="/hello",} 0.967
# HELP http_timer_seconds_max 接口请求耗时统计
# TYPE http_timer_seconds_max gauge
http_timer_seconds_max{application="web-service",uri="/hello",} 0.464
1
2
3
4
5
6
7
8
9

micrometer-core内置了计时器的注解@Timed,spring框架可以直接使用该注解处理维护web请求添加计时指标的支持,而使用的方式也比较简单:

  1. 全局配置timer注解的切面TimedAspect,该切面底层会声明一个维护类名和方法名的timer计时器
  2. 在需要计时支持的接口上注明@Timed注解

由此,当接口触发调用时,切面就会将请求耗时信息维护到时间序列中。

对应切面配置如下:

@Configuration
public class TimedConfiguration {
    @Bean
    public TimedAspect timedAspect(MeterRegistry registry) {
        return new TimedAspect(registry);
    }
}
1
2
3
4
5
6
7

步入TimedAspect构造方法也印证了笔者的说法,基于类名和方法名构建计时器:

public TimedAspect(MeterRegistry registry) {
        this(registry, pjp ->
                Tags.of("class", pjp.getStaticPart().getSignature().getDeclaringTypeName(),
                        "method", pjp.getStaticPart().getSignature().getName())
        );
    }
1
2
3
4
5
6

使用时也只需在接口上注明@Timed注解即可:

@Timed
    @GetMapping("/function")
    public String function() {

       

        int sleepTime = RandomUtil.randomInt(1000);
        log.info("休眠 {} 毫秒", sleepTime);
        ThreadUtil.sleep(sleepTime);
        reqFunctionCounter.increment();
        return "function";
    }
1
2
3
4
5
6
7
8
9
10
11
12

当我们尝试发起请求,并通过/actuator/prometheus映射查看,本次接口请求的总数、总耗时、最大耗时等信息都输出渲染了,后续我们就可以通过监控系统轮询采集这些指标进行速率聚合得到单位时间内最大耗时和平均耗时(文章后续系列会给出grafana的实践):

method_timed_seconds_count{application="web-service",class="com.sharkchili.controller.TestController",exception="none",method="function",} 1.0
method_timed_seconds_sum{application="web-service",class="com.sharkchili.controller.TestController",exception="none",method="function",} 0.360479125
# HELP method_timed_seconds_max  
# TYPE method_timed_seconds_max gauge
method_timed_seconds_max{application="web-service",class="com.sharkchili.controller.TestController",exception="none",method="function",} 0.360479125
1
2
3
4
5

这里补充说明一点,很多读者可能会基于该计量器进行别的用途,在使用时需要注意:

  1. 避免记录负值
  2. 避免记录过多长耗时导致总时间溢出溢出
  3. 最大值不要超过Long.MAX_VALUE

# 计数器

计数器只能报告单一的指标,允许单调递增且不能为负数,所以计数器常用语统计一些事件的增长速率,需要特别注意一点,永远不要手动用计数器实现那些timer或者DistributionSummary能够做到的计数汇总,例如手动用计数器维护请求接口总数并计算接口平均耗时。

这里我们counter的最佳实践,也给出一个自服务运行以来的请求总数,使用方式也比较简单和计时器类似,全局声明counter并将其注册到注册器中:

//统计hello请求的计数器
private Counter reqHelloCounter;


@PostConstruct
    private void init() {
        //名称设置为http.requests,标签设置为uri为/hello,选用合适的名称辅助开发推断理解
        reqHelloCounter = registry.counter("http.requests", "uri", "/hello");
        
        //......
    }
1
2
3
4
5
6
7
8
9
10
11

当我向hello接口发起请求时,对应actual映射就会看到如下指标,可以看到hello映射的请求总数counter:

# HELP http_requests_total  
# TYPE http_requests_total counter
http_requests_total{application="web-service",uri="/hello",} 1.0
1
2
3

使用countrer时需要注意一下几点:

  1. counter是个自增计量器,对于需要进行计时和汇总的内容,优先考虑使用timer或DistributionSummary
  2. counter在应用重启后会重置为0
  3. counter适用于单调递增的计量需求,让监控系统分析增长速率

# 仪表

仪表是一个用于获取当前数值的句柄,典型的仪表包括集合映射大小或者出于运行状态的线程池,所以micrometer认为guages是一个应被采样而不可被设置的,即不可设置中间值(因未及时被监控系统采集就会丢失),它更多作用是在观察时才会动态变化的一个监控指标,这也是为什么guage常用于监控那些具有自然上限的属性。

gauge常的用法的也比较简单,再进行必要的声明和注册后,就像使用原子类一样,笔者这里就直接给出大体的用例就不多做介绍了:

//全局声明
private AtomicInteger myGauge ;

	@PostConstruct
    private void init() {
		myGauge= registry.gauge("numberGauge", new AtomicInteger(0));
	}


		
1
2
3
4
5
6
7
8
9
10

对应使用示例如下,可以直接原子set亦或者用cas进行自增或自减:


			//修改值
        myGauge.set(RandomUtil.randomInt(100));
        //自增
        myGauge.incrementAndGet();
        //自减
        myGauge.decrementAndGet();
1
2
3
4
5
6
7

# 分布式摘要

针对非时间指标的事件分布情况的分析,我们更推荐使用分布式摘要也就是DistributionSummary,例如我们需要统计每个接口请求的响应消息大小,就可以使用声明一个分布式摘要,并通过切面捕获这些信息,对应的全局声明代码示例如下


private DistributionSummary summary;

 @PostConstruct
    private void init() {
			//声明
			summary = DistributionSummary
                .builder("response.size")
                .description("消息大小") 
                .baseUnit("bytes") 
                .tags("url", "test") 
                .register(registry);

			//......
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

使用时,我们也只需获取切面返回的消息大小,并记录到summary即可,同时我们可以基于summary的常见api实时打印监控信息:


//使用
summary.record(byteSize);
        double max = summary.max();
        long count = summary.count();
        double avg = summary.mean();
        log.info("最大值:{},请求次数:{},平均值:{}", max, count, avg);
1
2
3
4
5
6
7

由此我们可以在actual上看到当前接口请求返回消息大小:

  1. 请求总数
  2. 总大小
  3. 最大值
# HELP response_size_bytes 消息大小
# TYPE response_size_bytes summary
response_size_bytes_count{application="web-service",url="/test",} 5.0
response_size_bytes_sum{application="web-service",url="/test",} 283.0
# HELP response_size_bytes_max 消息大小
# TYPE response_size_bytes_max gauge
response_size_bytes_max{application="web-service",url="/test",} 79.0
1
2
3
4
5
6
7

# 详解micrometer高级特性

# Meter Filters过滤

默认情况下我们spring应用都是暴露所有指标,如果我们希望过滤特定指标的话可以通过MeterFilter实现,使用方式也比较简单,通过MeterFilter的deny并声明过滤的表达式即可:

@Configuration
public class MeterFilterConfig {
    @Bean
    public MeterFilter meterFilter() {
        //过滤actual上jvm或者tomcat开头的监控指标
        return MeterFilter.deny(id -> id.getName().startsWith("jvm.")|| id.getName().startsWith("tomcat."));
    }
}

1
2
3
4
5
6
7
8
9

此时,guage就不会再输出显示这些指标了:

# 小结

本文介绍了监控系统的维维度性、速率聚合和发布,同时介绍了micrometer中常见的计时器、计数器和分布摘要并给出相应的实践,在此基础之上也给出了监控指标的过滤技巧,希望对你有帮助。

我是 SharkChili ,Java 开发者,Java Guide 开源项目维护者。欢迎关注我的公众号:写代码的SharkChili,也欢迎您了解我的开源项目 mini-redis:https://github.com/shark-ctrl/mini-redis (opens new window)。

为方便与读者交流,现已创建读者群。关注上方公众号获取我的联系方式,添加时备注加群即可加入。

# 参考

Micrometer 官方文档 (opens new window)

Micrometer 核心概念 详解 (opens new window)

服务监控 | 万字长文详解Micrometer (opens new window)

编辑 (opens new window)
上次更新: 2026/03/26, 01:05:31
SpringBoot集成Prometheus与Grafana监控
从micrometer计量器角度快速上手promQL

← SpringBoot集成Prometheus与Grafana监控 从micrometer计量器角度快速上手promQL→

最近更新
01
基于EasyExcel实现高效导出
03-25
02
从开源框架中学习那些实用的位运算技巧
03-25
03
浅谈分布式架构设计思想和常见优化手段
03-25
更多文章>
Theme by Vdoing | Copyright © 2025-2026 Evan Xu | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×
×