# 28 | 定向监控:怎样快速发现业务异常?
你好,我是高楼。这节课,我们来讲一讲定向监控。
其实全局监控和定向监控,我在三个专栏里都反复提过。你可能会问了,既然之前都写过了,为什么还要在这个专栏中专门再写一篇呢?毕竟凑字也不是我的风格,我还真是要写点不一样的东西。
在这一节课,我会主要介绍业务级的定向监控,也就是要实现业务代码级的监控。如果你想学习更多定向监控的知识,也可以看看我以前讲过的内容: [《性能测试场景:如何进行监控设计?》](https://time.geekbang.org/column/article/190132) 和 [《如何设计全局和定向监控策略?》](https://time.geekbang.org/column/article/361138) 。
话说回来,为什么要做业务级的定向监控呢?因为线上压测是有风险的,这个大家都知道。而做到业务级的定向监控可以降低压测风险。
业务级的定向监控对于刚入行的性能测试小白来说确实是一个新话题,但对于从事多年性能测试老鸟来说并不奇怪。那我们怎么具体落地呢,下面我们就来仔细看一看。
## 本地环境准备
在之前的课程里,我已经介绍了市场上常见的监控工具,这里我就结合 Prometheus SDK 来说说如何改造代码并落地业务 Metrics 埋点 。
> Prometheus 提供度量的四种基本类型包括:Counter,Gauge,Histogram,Summary。
这里,我主要演示电商项目下单业务的定向监控。我们会主要采集**订单成功次数、订单失败次数、订单金额总数**等业务的 Metrics,如果你还想扩展其它业务,也可以参考这一方法进行改造。
依照惯例,我还是先在本地搭建 demo 做技术预演,预演成功后再做真实系统改造。
首先,我们要搭建 Prometheus 集成 Grafana 的本地环境。具体的操作你可以参考下面三篇文章:
* [《性能监控之初识 Prometheus》](https://mp.weixin.qq.com/s/C6E0Ak-dokBtuTzTczZAaA)
* [《性能监控之 node\_exporter+Prometheus+Grafana 实现主机监控》](https://mp.weixin.qq.com/s/IakvrGQBzTSq_FPUF-pAYw)
* [《性能监控工具之Grafana+Prometheus+Exporters》](https://time.geekbang.org/column/article/190684)
环境搭建好之后,运行效果如下:
Prometheus 监控系统:
![图片](https://static001.geekbang.org/resource/image/70/72/707389253947f6528d96b34a6d809272.png?wh=1740x880)
Grafana 可视化看板:
![图片](https://static001.geekbang.org/resource/image/c4/b0/c456f5e15124a0b2de374d592df3fdb0.png?wh=752x492)
这样,我们用 Prometheus 集成的 Grafana 的本地环境就准备好了。
## demo 技术预演
下一步,我们就要搭建业务级的定向监控 demo了,你可以先看下这张逻辑图:
![图片](https://static001.geekbang.org/resource/image/61/f3/61805753c8f81f09a652b550546693f3.jpg?wh=1461x379)
一般情况下,使用 Prometheus SDK 做业务埋点主要有这几个步骤:
1. 创建 Prometheus Metric 数据项,可以创建一个自定义类;
2. 注册定义好的 Metric;
3. 在业务代码中埋点,对 TSDB 数据写入操作;
4. 提供 HTTP API 接口,让 Prometheus 定时来收集 Metric 数据。
这是一个通用的埋点套路,无论是 Golang 还是 Java 应用都是适应的。下面我们来看下它在demo项目中的具体实现:
* 新建 SpringBoot 工程
使用 SpringBoot(项目使用的框架) 集成 Prometheus SDK。这里我们要导入相关依赖:
```xml
io.micrometer
micrometer-registry-prometheus
1.7.4
```
* 修改 SpringBoot 全局配置文件
在 application.yml 文件中输入如下配置:
```yaml
spring:
application:
name: dunshan-prometh
server:
port: 8086
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
prometheus:
enabled: true
metrics:
tags:
application: ${spring.application.name}
```
改好配置文件之后,启动应用服务器,可以看到 Metric 的访问路径和端口号:
![图片](https://static001.geekbang.org/resource/image/ca/1d/caa0b2e053010219ba1640723e05691d.png?wh=752x248)
我们通过路径和端口号可以得到下面这个地址: [http://localhost:8086/actuator/prometheus](http://localhost:8086/actuator/prometheus) 。
启动工程,在浏览器中输入这个地址,结果显示如下:
![图片](https://static001.geekbang.org/resource/image/0c/e2/0cb7a0b475b38187729fb84d5b0ff3e2.png?wh=752x203)
可以看到, Actuator Metrics 数据已经正常显示出来了。
* 配置 Prometheus 拉取数据
接下来,在 Prometheus 主程序的配置文件中填写上面的 Metrics 接口信息。
在 prometheus.yml 写入的配置你可以参考下面的代码:
```yaml
global:
scrape_interval: 15s
scrape_configs:
- job_name: "prometheus"
static_configs:
- targets: ["localhost:9090"]
- job_name: "dunshan-prometh"
metrics_path: "/actuator/prometheus"
static_configs:
- targets: ["localhost:8086"]
```
配置完之后,我们重启 Prometheus 服务,在浏览器中输入地址: [http://ip:9090](http://ip:9090) ,在控制台的 Status 菜单栏中选择 Targets:
![图片](https://static001.geekbang.org/resource/image/da/ed/da2dd20b76883b8177228a84b01955ed.png?wh=1106x610)
这样就能看到目前 Prometheus 收集的 Metrics 的路径了:
![图片](https://static001.geekbang.org/resource/image/db/f8/dbc97b4ce6e252092e0b3a2d70a961f8.png?wh=752x274)
再次点击 [http://localhost:8086/actuator/prometheus](http://localhost:8086/actuator/prometheus) ,显示的信息如下:
![图片](https://static001.geekbang.org/resource/image/3c/7b/3cc9f80a95f8fe2f31a8e8d7b2fbc27b.png?wh=712x466)
这部分信息告诉我们,应用服务 Metrics 已经被 Prometheus 成功定时收集起来了。
接下来就要在应用服务中埋点收集数据了。在我们的业务服务里面,需要统计的 Metrics 有订单成功次数、订单失败次数和订单金额总数。我们可以通过对订单类中生成的订单号进行埋点来实现。
在项目中新增 PrometheusCustomMonitor 类,初始化业务 Metric。具体的代码参考如下:
```java
package com.dunshan.prometh.controller;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* @author dunshan
* @program: dunshan-pef
* @description: 自定义下单接口采集类
* @date 2021-09-25 10:13:46
*/
@Component
public class PrometheusCustomMonitor {
/**
* 下单次数
*/
private Counter orderCount;
/**
* 下单失败数
*/
private Counter failureCount;
/**
* 金额统计
*/
private DistributionSummary amountSum;
private final MeterRegistry registry;
@Autowired
public PrometheusCustomMonitor(MeterRegistry registry) {
this.registry = registry;
}
@PostConstruct
private void init() {
//统计下单数
orderCount = registry.counter("order_request_count", "order", "mall-order");
//统计失败数
failureCount = registry.counter("order_failure_count", "order", "mall-order");
//统计金额数据
amountSum = registry.summary("order_amount_sum", "orderAmount", "mall-order");
}
public Counter getOrderCount() {
return orderCount;
}
public Counter getFailureCount() {
return failureCount;
}
public DistributionSummary getAmountSum() {
return amountSum;
}
}
```
声明了 Prometheus Metric 类之后,只要在业务方直接调用就可以了。这里我们直接在 Controller 层中增加请求进行模拟测试,从而实现对业务的埋点,收集我们想要的 Metrics 数据。
* 模拟业务请求
这里我们模拟调用请求,增加订单成功次数、订单失败次数和订单金额总数。
具体代码参考如下:
```java
package com.dunshan.prometh.controller;
import com.dunshan.prometh.popj.Result;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Random;
/**
* @author dunshan
* @program: dunshan-pef
* @description: 自定义监控采集控制层
* @date 2021-09-25 21:33:59
*/
@Log4j2
@Controller
@RestController
public class IndexPromenthController {
@Resource
private PrometheusCustomMonitor customMonitor;
@GetMapping("/")
public Result IndexPage() {
HashMap