Spring Boot如何自定义监控指标

1.创建项目

pom.xml引入相关依赖

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 	<modelVersion>4.0.0</modelVersion> 	<groupId>com.olive</groupId> 	<artifactId>prometheus-meter-demo</artifactId> 	<version>0.0.1-SNAPSHOT</version> 	<parent> 		<groupId>org.springframework.boot</groupId> 		<artifactId>spring-boot-starter-parent</artifactId> 		<version>2.3.7.RELEASE</version> 		<relativePath /> 	</parent> 	<properties> 		<java.version>1.8</java.version> 		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> 		<spring-boot.version>2.3.7.RELEASE</spring-boot.version> 	</properties> 	<dependencies> 		<dependency> 			<groupId>org.springframework.boot</groupId> 			<artifactId>spring-boot-starter-aop</artifactId> 		</dependency> 		<dependency> 			<groupId>org.springframework.boot</groupId> 			<artifactId>spring-boot-starter-web</artifactId> 		</dependency> 		<dependency> 			<groupId>org.springframework.boot</groupId> 			<artifactId>spring-boot-starter-actuator</artifactId> 		</dependency> 		<!-- Micrometer Prometheus registry  --> 		<dependency> 			<groupId>io.micrometer</groupId> 			<artifactId>micrometer-registry-prometheus</artifactId> 		</dependency> 	</dependencies> 	<dependencyManagement> 		<dependencies> 			<dependency> 				<groupId>org.springframework.boot</groupId> 				<artifactId>spring-boot-dependencies</artifactId> 				<version>${spring-boot.version}</version> 				<type>pom</type> 				<scope>import</scope> 			</dependency> 		</dependencies> 	</dependencyManagement> </project> 

2.自定义指标

  • 方式一

直接使用micrometer核心包的类进行指标定义和注册

package com.olive.monitor;  import javax.annotation.PostConstruct;  import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;  import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.DistributionSummary; import io.micrometer.core.instrument.MeterRegistry;  @Component public class NativeMetricsMontior {  	/** 	 * 支付次数 	 */ 	private Counter payCount;  	/** 	 * 支付金额统计 	 */ 	private DistributionSummary payAmountSum;  	@Autowired 	private MeterRegistry registry;  	@PostConstruct 	private void init() { 		payCount = registry.counter("pay_request_count", "payCount", "pay-count"); 		payAmountSum = registry.summary("pay_amount_sum", "payAmountSum", "pay-amount-sum"); 	}  	public Counter getPayCount() { 		return payCount; 	}  	public DistributionSummary getPayAmountSum() { 		return payAmountSum; 	}  } 
  • 方式二

通过引入micrometer-registry-prometheus包,该包结合prometheus,对micrometer进行了封装

<dependency> 			<groupId>io.micrometer</groupId> 			<artifactId>micrometer-registry-prometheus</artifactId> 		</dependency> 

同样定义两个metrics

package com.olive.monitor;  import javax.annotation.PostConstruct;  import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;  import io.prometheus.client.CollectorRegistry; import io.prometheus.client.Counter;  @Component public class PrometheusMetricsMonitor {  	/** 	 * 订单发起次数 	 */ 	private Counter orderCount;  	/** 	 * 金额统计 	 */ 	private Counter orderAmountSum; 	 	@Autowired 	private CollectorRegistry registry; 	 	 	@PostConstruct 	private void init() { 		orderCount = Counter.build().name("order_request_count") 				.help("order request count.") 				.labelNames("orderCount") 				.register(); 		orderAmountSum = Counter.build().name("order_amount_sum") 				.help("order amount sum.") 				.labelNames("orderAmountSum") 				.register(); 		registry.register(orderCount); 		registry.register(orderAmountSum); 	}  	public Counter getOrderCount() { 		return orderCount; 	}  	public Counter getOrderAmountSum() { 		return orderAmountSum; 	}  } 

prometheus 4种常用Metrics

Counter

连续增加不会减少的计数器,可以用于记录只增不减的类型,例如:网站访问人数,系统运行时间等。

对于Counter类型的指标,只包含一个inc()的方法,就是用于计数器+1.

一般而言,Counter类型的metric指标在冥冥中我们使用_total结束,如http_requests_total.

Gauge

可增可减的仪表盘,曲线图

对于这类可增可减的指标,用于反应应用的当前状态。

例如在监控主机时,主机当前空闲的内存大小,可用内存大小等等。

对于Gauge指标的对象则包含两个主要的方法inc()和dec(),用于增加和减少计数。

Histogram

主要用来统计数据的分布情况,这是一种特殊的metrics数据类型,代表的是一种近似的百分比估算数值,统计所有离散的指标数据在各个取值区段内的次数。例如:我们想统计一段时间内http请求响应小于0.005秒、小于0.01秒、小于0.025秒的数据分布情况。那么使用Histogram采集每一次http请求的时间,同时设置bucket。

Summary

Summary和Histogram非常相似,都可以统计事件发生的次数或者大小,以及其分布情况,他们都提供了对时间的计数_count以及值的汇总_sum,也都提供了可以计算统计样本分布情况的功能,不同之处在于Histogram可以通过histogram_quantile函数在服务器计算分位数。而Sumamry的分位数则是直接在客户端进行定义的。因此对于分位数的计算,Summary在通过PromQL进行查询的时候有更好的性能表现,而Histogram则会消耗更多的资源,但是相对于客户端而言Histogram消耗的资源就更少。用哪个都行,根据实际场景自由调整即可。

3. 测试

定义两个controller分别使用NativeMetricsMontiorPrometheusMetricsMonitor

package com.olive.controller;  import java.util.Random;  import javax.annotation.Resource;  import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;  import com.olive.monitor.NativeMetricsMontior;  @RestController public class PayController {  	@Resource 	private NativeMetricsMontior monitor;  	@RequestMapping("/pay") 	public String pay(@RequestParam("amount") Double amount) throws Exception { 		// 统计支付次数 		monitor.getPayCount().increment();  		Random random = new Random(); 		//int amount = random.nextInt(100); 		if(amount==null) { 			amount = 0.0; 		} 		// 统计支付总金额 		monitor.getPayAmountSum().record(amount); 		return "支付成功, 支付金额: " + amount; 	}  } 
package com.olive.controller;  import java.util.Random;  import javax.annotation.Resource;  import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;  import com.olive.monitor.PrometheusMetricsMonitor;  @RestController public class OrderController {  	@Resource 	private PrometheusMetricsMonitor monitor;  	@RequestMapping("/order") 	public String order(@RequestParam("amount") Double amount) throws Exception { 		// 订单总数 		monitor.getOrderCount() 			.labels("orderCount") 			.inc();  		Random random = new Random(); 		//int amount = random.nextInt(100); 		if(amount==null) { 			amount = 0.0; 		} 		// 统计订单总金额 		monitor.getOrderAmountSum() 			.labels("orderAmountSum") 			.inc(amount); 		return "下单成功, 订单金额: " + amount; 	}  } 

启动服务

访问http://127.0.0.1:9595/actuator/prometheus;正常看到监测数据

Spring Boot如何自定义监控指标

改变amount多次方式http://127.0.0.1:8080/order?amount=100http://127.0.0.1:8080/pay?amount=10后;再访问http://127.0.0.1:9595/actuator/prometheus。查看监控数据

Spring Boot如何自定义监控指标

4.项目中的应用

项目中按照上面说的方式进行数据埋点监控不太现实;在spring项目中基本通过AOP进行埋点监测。比如写一个切面Aspect;这样的方式就非常友好。能在入口就做了数据埋点监测,无须在controller里进行代码编写。

package com.olive.aspect;  import java.time.LocalDate; import java.util.concurrent.TimeUnit;  import javax.servlet.http.HttpServletRequest;  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.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes;  import io.micrometer.core.instrument.Metrics;  @Aspect @Component public class PrometheusMetricsAspect {      // 切入所有controller包下的请求方法     @Pointcut("execution(* com.olive.controller..*.*(..))")     public void controllerPointcut() {     }      @Around("controllerPointcut()")     public Object MetricsCollector(ProceedingJoinPoint joinPoint) throws Throwable {          HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();         String userId = StringUtils.hasText(request.getParameter("userId")) ?          		request.getParameter("userId") : "no userId";                  // 获取api url         String api = request.getServletPath();         // 获取请求方法         String method = request.getMethod();         long startTs = System.currentTimeMillis();         LocalDate now = LocalDate.now();         String[] tags = new String[10];         tags[0] = "api";         tags[1] = api;         tags[2] = "method";         tags[3] = method;         tags[4] = "day";         tags[5] = now.toString();         tags[6] = "userId";         tags[7] = userId;                  String amount = StringUtils.hasText(request.getParameter("amount")) ?          		request.getParameter("amount") : "0.0";                  tags[8] = "amount";         tags[9] = amount;         // 请求次数加1         //自定义的指标名称:custom_http_request_all,指标包含数据         Metrics.counter("custom_http_request_all", tags).increment();         Object object = null;         try {             object = joinPoint.proceed();         } catch (Exception e) {             //请求失败次数加1             Metrics.counter("custom_http_request_error", tags).increment();             throw e;         } finally {             long endTs = System.currentTimeMillis() - startTs;             //记录请求响应时间            Metrics.timer("custom_http_request_time", tags).record(endTs, TimeUnit.MILLISECONDS);         }         return object;     } } 

编写好切面后,重启服务;访问controller的接口,同样可以进行自定义监控指标埋点

Spring Boot如何自定义监控指标

发表评论

相关文章