日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發布時間:2023/12/10 编程问答 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 从源码角度分析下 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 源碼, 做了一個簡單場景的嘗試,其實可做的方向還很多。

其實關于方式的選擇,留著以后說吧。

?

總結

以上是生活随笔為你收集整理的从源码角度分析下 micrometer 自定义 metrics endpoint 和 springboot actuator的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。