从源码角度分析下 micrometer 自定义 metrics endpoint 和 springboot actuator

大家好,我是烤鸭:

    今天分享下  micrometer 的源码,和springboot集成 自定义endpoint 的使用。

1.  文档信息

官方文档:

http://micrometer.io/docs

github:

https://github.com/micrometer-metrics/micrometer

springboot集成:
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#production-ready-metrics

2.  简单介绍

监测信息包括 jvm、memory、cpu、tomcat 等等。

网上关于 springboot 集成和使用,也有很多文章,这里就不赘述了。

本篇文章主要是源码分析和简单场景使用。

本文代码地址:

https://gitee.com/fireduck_admin/micrometer-demo/tree/master

 

3.  源码分析

3.1  micrometer-core 包的源码

说几个核心类:

MeterRegistry.java

注册表中心用来管理应用的注册表,再遍历注册表获取指标。

public abstract class MeterRegistry {protected final Clock clock;private final Object meterMapLock = new Object();private volatile MeterFilter[] filters = new MeterFilter[0];private final List<Consumer<Meter>> meterAddedListeners = new CopyOnWriteArrayList<>();private final List<Consumer<Meter>> meterRemovedListeners = new CopyOnWriteArrayList<>();private final Config config = new Config();private final More more = new More();//...
}

MeterBinder.java

绑定容器内部测量的父类接口。(所有需要测量类的重写这个接口就行)

/*** Binders register one or more metrics to provide information about the state* of some aspect of the application or its container.* <p>* Binders are enabled by default if they source data for an alert* that is recommended for a production ready app.*/
public interface MeterBinder {void bindTo(@NonNull MeterRegistry registry);
}

Gauge

Meter的子类,Meter是测量指标(可以理解为值对象),而Gauge是指标的瞬时值(普通的对象)。

/*** A gauge tracks a value that may go up or down. The value that is published for gauges is* an instantaneous sample of the gauge at publishing time.** @author Jon Schneider*/
public interface Gauge extends Meter {/*** @param name The gauge's name.* @param obj  An object with some state or function which the gauge's instantaneous value*             is determined from.* @param f    A function that yields a double value for the gauge, based on the state of*             {@code obj}.* @param <T>  The type of object to gauge.* @return A new gauge builder.*/static <T> Builder<T> builder(String name, @Nullable T obj, ToDoubleFunction<T> f) {return new Builder<>(name, obj, f);}//...
}

我们以其中某个类分析下:

JvmThreadMetrics.java

监测 JVM 线程变化的

@Overridepublic void bindTo(MeterRegistry registry) {ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();Gauge.builder("jvm.threads.peak", threadBean, ThreadMXBean::getPeakThreadCount).tags(tags).description("The peak live thread count since the Java virtual machine started or peak was reset").baseUnit(BaseUnits.THREADS).register(registry);Gauge.builder("jvm.threads.daemon", threadBean, ThreadMXBean::getDaemonThreadCount).tags(tags).description("The current number of live daemon threads").baseUnit(BaseUnits.THREADS).register(registry);Gauge.builder("jvm.threads.live", threadBean, ThreadMXBean::getThreadCount).tags(tags).description("The current number of live threads including both daemon and non-daemon threads").baseUnit(BaseUnits.THREADS).register(registry);for (Thread.State state : Thread.State.values()) {Gauge.builder("jvm.threads.states", threadBean, (bean) -> getThreadStateCount(bean, state)).tags(Tags.concat(tags, "state", getStateTagValue(state))).description("The current number of threads having " + state + " state").baseUnit(BaseUnits.THREADS).register(registry);}}

创建 Gauge 内部类builder 和 当前的 registry 绑定,我们看下方法注释怎么说的。

对单例的注册表添加一个指标测量对象,或者返回一个已存在的。返回的是当前注册表唯一的,每个注册表保证相同名字和标签只创建一个指标测量对象。

	/*** Add the gauge to a single registry, or return an existing gauge in that registry. The returned* gauge will be unique for each registry, but each registry is guaranteed to only create one gauge* for the same combination of name and tags.** @param registry A registry to add the gauge to, if it doesn't already exist.* @return A new or existing gauge.*/public Gauge register(MeterRegistry registry) {return registry.gauge(new Meter.Id(name, tags, baseUnit, description, Type.GAUGE, syntheticAssociation), obj,strongReference ? new StrongReferenceGaugeFunction<>(obj, f) : f);}

上面就是一个收集信息的过程,简单来说 收集到的信息放到注册表,需要的时候来取。看一下springboot的actuator的源码。

3.2   spring-boot-starter-actuator 的源码 

先说一下 endpoint 这个关键的包。

其中一个 Endpoint 注解(执行器断点),带有这个注解的执行器会被公开。

/*** Identifies a type as being an actuator endpoint that provides information about the* running application. Endpoints can be exposed over a variety of technologies including* JMX and HTTP.**/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Endpoint {/*** The id of the endpoint (must follow {@link EndpointId} rules).* @return the id* @see EndpointId*/String id() default "";/*** If the endpoint should be enabled or disabled by default.* @return {@code true} if the endpoint is enabled by default*/boolean enableByDefault() default true;}

简单来说:

带有这个注解的类,会被增加到servlet,路径是 basePath+注解的id属性。源码是这个类。

EndpointDiscoverer.createEndpointBeans

private Collection<EndpointBean> createEndpointBeans() {Map<EndpointId, EndpointBean> byId = new LinkedHashMap<>();String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.applicationContext,Endpoint.class);for (String beanName : beanNames) {if (!ScopedProxyUtils.isScopedTarget(beanName)) {EndpointBean endpointBean = createEndpointBean(beanName);EndpointBean previous = byId.putIfAbsent(endpointBean.getId(), endpointBean);Assert.state(previous == null, () -> "Found two endpoints with the id '" + endpointBean.getId() + "': '"+ endpointBean.getBeanName() + "' and '" + previous.getBeanName() + "'");}}return byId.values();
}

找到带有Endpoint注解的类,比如 MetricsEndpoint.class,metric 就是请求 /actuator/metrics/jvm.gc.max.data.size 调用的方法。

@Endpoint(id = "metrics")
public class MetricsEndpoint {//...@ReadOperationpublic MetricResponse metric(@Selector String requiredMetricName, @Nullable List<String> tag) {List<Tag> tags = parseTags(tag);Collection<Meter> meters = findFirstMatchingMeters(this.registry, requiredMetricName, tags);if (meters.isEmpty()) {return null;}Map<Statistic, Double> samples = getSamples(meters);Map<String, Set<String>> availableTags = getAvailableTags(meters);tags.forEach((t) -> availableTags.remove(t.getKey()));Meter.Id meterId = meters.iterator().next().getId();return new MetricResponse(requiredMetricName, meterId.getDescription(), meterId.getBaseUnit(),asList(samples, Sample::new), asList(availableTags, AvailableTag::new));}//...}

知道Endpoint,尝试写自己的监控指标。 

4.  实现自定义micrometer

简单点的方式:

自定义 RedisMetric 重写 bindTo方法,访问 /metric/redis.get.info 就可以看到指标了

package com.maggie.measure.micrometer.metric;import java.util.concurrent.atomic.AtomicInteger;import io.micrometer.core.instrument.*;
import io.micrometer.core.instrument.binder.MeterBinder;
import org.springframework.stereotype.Component;@Component
public class RedisMetric implements MeterBinder {public static AtomicInteger atomicInteger = new AtomicInteger(0);@Overridepublic void bindTo(MeterRegistry meterRegistry) {Gauge.builder("redis.get.count", atomicInteger, c -> c.get()).tags("host", "localhost").description("demo of custom meter binder").register(meterRegistry);}}

实现一个监控redis get/set 方法的次数统计。

访问 http://localhost:8081/get 

结果如图,value就是调用的次数 11次。

稍微复杂点,实现拦截 redis get/set 方法,统计get/set 方法 的key以及每个key 的调用次数。

自定义 endponit 实现,自定义的好处是路径变成了 endpoint的id,比如我下边的路径就是 ./redis

/*** @program: micrometer-demo* @description: redis监控断点*/
@Component
@Endpoint(id = "redis")
public class RedisRegistryEndpoint {private final MeterRegistry registry;public RedisRegistryEndpoint(MeterRegistry registry) {this.registry = registry;}@ReadOperationpublic String home() {Set<String> set = new HashSet<>();set.add("redis.get.info");set.add("redis.set.info");return JSONObject.toJSONString(set);}@ReadOperationpublic String metric(@Selector String tagName) {tagName = tagName.replaceAll("\\.", "").replaceAll("redis", "").replaceAll("info", "");return JSONObject.toJSONString(RedisMetric.param.get(tagName));}
}

统计次数和key的是通过aop实现的,由于没办法直接拦截 redisTemplate 所以我封装了一个redis工具类方法。

package com.maggie.measure.micrometer.aspect;import com.maggie.measure.micrometer.metric.RedisMetric;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;import java.util.HashMap;
import java.util.Map;@Aspect
@SuppressWarnings("all")
@Component("redisApiAspect")
public class RedisApiAspect {public static Map incrMap = new HashMap<>();@Pointcut("execution(public * com.maggie.measure.micrometer.service.RedisOpsValService.*(..))")private void redisApi() {}@Around("redisApi()")public Object doProfiling(ProceedingJoinPoint point) throws Throwable {long initTime = System.currentTimeMillis();long sTime = initTime, eTime = initTime;MethodSignature methodSignature = null;Object proceed = null;try {methodSignature = (MethodSignature) point.getSignature();} finally {String met = methodSignature.getName(); // 拦截方法名称Object[] args = point.getArgs(); // 拦截的方法参数proceed = point.proceed();if ("get".equals(met)) {RedisMetric.atomicGetInteger.getAndIncrement();}if ("set".equals(met)) {RedisMetric.atomicSetInteger.getAndIncrement();}if (RedisMetric.param.get(met) != null) {Map<String, Object> metMap = RedisMetric.param.get(met);incrMap.put(met + "incr", Double.valueOf((Integer) metMap.getOrDefault(met + "incr", 0) + 1));int incr = (Integer) incrMap.getOrDefault(met + args[0] + "incr", 0) + 1;incrMap.put(met + args[0] + "incr", incr);if (args != null && args[0] instanceof String) {metMap.put((String) args[0], incr);}} else {Map<String, Object> metMap = new HashMap<>();incrMap.put(met + "incr", Double.valueOf((Integer) metMap.getOrDefault(met + "incr", 0) + 1));int incr = (Integer) incrMap.getOrDefault(met + args[0] + "incr", 0) + 1;if (incrMap.get(met + args[0] + "incr") == null) {incrMap.put(met + args[0] + "incr", incr);}if (args != null && args[0] instanceof String) {metMap.put((String) args[0], incr);}RedisMetric.param.put(met, metMap);}}return proceed;}
}

结果如图:

可以看出 查哪些参数(get.info,set.info),以及 get/set 的key和单个key的调用次数。

 

5.  总结

实现系统监控有很多方式,micrometer-metrics 是个不错的开源框架,而且springboot 封装的也挺好的。关于拉式(提供接口,外部调用)还是推式(上报,http/socket 等等)方案的选择,还是看自己的业务场景。量大(服务器数量多且服务多)的时候无论采用哪种都不太好,不仅对性能损耗,而且维护麻烦,不易升级。这里只是看了 metrics 源码, 做了一个简单场景的尝试,其实可做的方向还很多。

其实关于方式的选择,留着以后说吧。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/412634.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

云打码

1、云打码平台注册开发者模式用户登录 2、建立项目&#xff0c;下载项目案列代码接口 代码如下&#xff1a; 1 import http.client, mimetypes, urllib, json, time, requests2 3 ######################################################################4 5 class YDMHttp:6 …

Perhaps you are running on a JRE rather than a JDK? 关于 idea maven 缓存的问题 清理 idea maven 缓存

大家好&#xff0c;我是烤鸭&#xff1a; 1. 先检查是否环境变量的问题 参考这篇 https://blog.csdn.net/mingjie1212/article/details/106963143 2. 如果确定环境变量没问题 执行maven install的时候报错&#xff0c;Perhaps you are running on a JRE rather than a JDK …

swift - 使用系统app导航

import UIKit//1.导入框架 import MapKitclass ViewController: UIViewController {lazy var geoCoder : CLGeocoder {return CLGeocoder()}()override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {geoCoder.geocodeAddressString("广州&…

springcloud gateway 使用nacos 作为配置中心 和 注册中心

大家好&#xff0c;我是烤鸭&#xff1a; 今天分享下 springcloud gateway 使用nacos作为配置中心和注册中心&#xff0c;主要是还是配置中心。 源码下载&#xff1a; https://gitee.com/fireduck_admin/springcloud-gateway-nacos-demo 1. 本地部署nacos 下载 https://g…

springboot 拦截器的坑 WebMvcConfigurationSupport 失效

大家好&#xff0c;我是烤鸭&#xff1a; 今天遇到一个拦截器失效的问题&#xff0c;具体看源码分析下。 环境: springboot 2.x spring 5.x 1. 先说下业务场景 需求是对请求进入时和离开时对和线程id绑定&#xff0c;用的Threadlocal&#xff0c;现在有一个问题&#xff0…

skywalking 源码解析——多线程变量传递 EnhancedInstance

大家好&#xff0c;我是烤鸭&#xff1a; 今天分享下 skywalking源码&#xff0c;正好自己用到相关的内容了。 1. 拦截点 三个主要的拦截器、构造方法、静态方法和示例方法&#xff0c;每个切面里都可以重写这些方法&#xff0c;并且指定进入的拦截器。 2. trace 相关内容 …

2018-2019-1 20165303 实验五 通讯协议设计

任务一 Linux下OpenSSL的安装与使用 前往OpenSSL官网&#xff0c;选择打开OpenSSL源码下载地址&#xff0c;按照下图所示方法下载压缩包“openssl-master.zip Linux下使用unzip openssl-master.zip命令解压 安装的具体命令如下&#xff1a;$ ./config$ make$ make test$ make i…

从字节码看 finally关键字、异常表

大家好&#xff0c;我是烤鸭&#xff1a; 今天说下 finally 这个关键字。 1. 认识finally finally 总是跟 try、catch一起出现&#xff0c;finally是执行方法结束一定要执行的代码&#xff0c;比如流关闭等等。 finally是如何实现在异常捕捉之后保证执行 finally 代码块里的…

Unhandled exception: org.springframework.beans.factory.BeanDefinitionStoreException

大家好&#xff0c;我是烤鸭&#xff1a; ​ 使用 idea 莫名其妙的错误。 无法编译 异常已经 catch 住了&#xff0c;提示明显是有问题的。 再看一下 maven install 的结果 Error:(99, 100) java: 无法访问org.springframework.core.env.EnvironmentCapable 找不到org.spr…

springcloud gateway 源码解析、请求响应流程、第三方响应结果在 gateway 的经过

大家好&#xff0c;我是烤鸭&#xff1a; 1. 官方介绍 官方文档&#xff1a; 看的是 2.2.5.RELEASE 版本的 https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/ 看一下官方这段说明&#xff0c;gateway 使用的是 webflux 和 reactor&#x…

改logback logstash-logback-encoder 框架本身的日志级别

大家好&#xff0c;我是烤鸭&#xff1a; 最近遇到一个问题&#xff0c;想把logback框架本身的日志级别修改&#xff0c;需要 logstash-logback-encoder 6.1 以上的版本才可以。 直接上代码 这里修改的不是业务日志级别&#xff0c;是 logback 框架本身(确切地说是 logstash…

idea首次创建新模块的详细操作

依赖网址:https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api/3.1.0 https://mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api/2.3.1 https://mvnrepository.com/artifact/org.springframework/spring-webmvc 创建文件夹 修改版本号 配…

markdown 常用语法总结 - 个人版

这里并不是要总结所有的 markdown 语法&#xff0c;只是总结笔者自己撰写博客时常用的一些 markdown 语法。 1 图片设置 1.1 设置图片位置 利用markdown在编写文档时插入图片是默认靠左&#xff0c;有些时候将图片设置为居中时可以更加的美观&#xff0c;这时就需要在图片的信息…

PostgreSQL 、springboot 、spring data jpa 集成

项目地址&#xff1a;https://gitee.com/zhxs_code/PostgreSQL_springboot_jpa_demo.git 增删查改都已经实现。 重点部分&#xff1a; 1.定义自己的方言。 1 package com.zxl.postgrespringdemo.config.dialect;2 3 import org.hibernate.dialect.PostgreSQL94Dialect;4 import…

RedisTemplate value序列化导致的问题

大家好&#xff0c;我是烤鸭&#xff1a; ​ 今天分享一个redisTemplate 使用时&#xff0c;value 序列化的问题。 1. 问题描述 其实我最开始遇到的问题是&#xff1a; stringRedisTemplate.opsForSet().isMember(key,value)一直返回false问题&#xff0c;下边博客给出了…

[css] 如何实现换肤功能?

[css] 如何实现换肤功能&#xff1f; css 换肤常见方案 是通过 less/sass/postcss 等css 预处理器&#xff0c;通过它们自身的变量用法&#xff0c;设置不同变量&#xff0c;生成不同的主题样式&#xff0c;但是这些样式都是会被打包成常量&#xff0c;我们只能在编译之前修改…

日志 中文乱码、nacos 中文乱码、saltstack 中文乱码、docker中文乱码

大家好&#xff0c;我是烤鸭&#xff1a; ​ 今天分享一个 saltstack 中文乱码 的问题。 问题说明 由于项目之前没有接入公司的发布系统&#xff0c;今天接入之后发现日志乱码&#xff0c;不仅如此&#xff0c;从nacos获取到的中文参数也是乱码。于是猜想是发布系统遗留了一…

redis出现过多command 慢查询slowlog出现command命令

大家好&#xff0c;我是烤鸭&#xff1a; 今天分享一个问题&#xff0c;一个关于redis slowlog&#xff0c;执行过多 command命令的问题。 问题来源 所有走redis 的接口tp99和平均耗时是原来的两倍不止&#xff0c;运维说redis 的qps也翻倍了。查了下slowlog&#xff0c;发现…

springcloud gateway 使用nacos 动态过滤器 记一次线上网关升级cpu升高的问题

大家好&#xff0c;我是烤鸭&#xff1a; ​ 网关升级&#xff0c;想使用 springcloud gateway nacos 动态过滤器配置(原来是硬编码的方式)&#xff0c;升级之后出了一些问题(cpu升高&#xff0c;ygc频繁)&#xff0c;记录一下。 关于 springcloud gateway 集成 nacos 可以看…

【1】生产者-消费者模型的三种实现方式

(手写生产者消费者模型&#xff0c;写BlockingQueue较简便 ) 1、背景 生产者生产数据到缓冲区中&#xff0c;消费者从缓冲区中取数据。 如果缓冲区已经满了&#xff0c;则生产者线程阻塞&#xff1b; 如果…