手寫!Controller接口性能監(jiān)控
1. 簡介
本文將介紹如何在Spring Boot應(yīng)用中通過Spring Boot Actuator和Prometheus來監(jiān)控任意API接口的調(diào)用耗時情況。通過Actuator提供度量metrics功能,我們能夠結(jié)合AOP輕松實現(xiàn)API接口運行時性能指標(biāo)。結(jié)合Prometheus這一強大的監(jiān)控系統(tǒng),我們能夠?qū)崟r地查看和分析API接口的調(diào)用耗時,進而評估應(yīng)用的性能狀況。
2. 環(huán)境準(zhǔn)備
2.1 依賴管理
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<scope>runtime</scope>
</dependency>
引入Prometheus依賴包,將所有的指標(biāo)數(shù)據(jù)導(dǎo)出為Prometheus格式。Prometheus會通過actuator接口拉取數(shù)據(jù)。同時還會注冊一個/prometheus接口。
配置文件
management:
endpoints:
web:
base-path: /ac
exposure:
include: '*'
暴露所有的端點。
3. 實戰(zhàn)案例
本案例統(tǒng)計Controller接口的耗時情況,為了簡單我們通過注解的方式標(biāo)注需要統(tǒng)計的方法。
3.1 定義注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Monitor {
// 作用是為不同的接口打標(biāo)記
String[] tags() default {} ;
}
其實你還可以定義比如:指標(biāo)名的屬性,這樣可以為不同的Controller做統(tǒng)計。
3.2 定義切面
該切面的作用用來攔截所有使用了@Monitor注解的Controller方法,通過環(huán)繞通知進行計時處理。
@Component
@Aspect
public class MonitorAspect {
private final MeterRegistry meterRegistry ;
// 度量名稱(你可以通過注解自定義)
private static final String API_TIMER_METER_NAME = "myapp.api.timer" ;
public MonitorAspect(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry ;
}
@Pointcut("@annotation(monitor)")
private void pcMonitor(Monitor monitor) {} ;
@Around("pcMonitor(monitor)")
public Object around(ProceedingJoinPoint pjp, Monitor monitor) throws Throwable {
Timer.Sample sample = Timer.start(this.meterRegistry) ;
String[] tags = monitor.tags() ;
Object ret = null ;
Throwable ex = null ;
try {
ret = pjp.proceed() ;
} catch (Throwable th) {
ex = th ;
throw th ;
} finally {
List<String> listTags = new ArrayList<>() ;
listTags.addAll(Arrays.asList(tags)) ;
// 出現(xiàn)異常也會將異常名稱打入tag
if (Objects.nonNull(ex)) {
listTags.add(ex.getClass().getSimpleName()) ;
}
Timer timer = meterRegistry.timer(API_TIMER_METER_NAME, listTags.toArray(new String[0])) ;
sample.stop(timer) ;
}
return ret ;
}
}
以上切面非常的簡單,統(tǒng)計方法執(zhí)行的耗時情況。
3.3 定義接口
@Service
public class UserService {
private static final List<User> DATAS = List.of(
new User(1L, "張三", "男", 22),
new User(2L, "李四", "男", 23),
new User(3L, "王五", "女", 22),
new User(4L, "趙六", "男", 32)) ;
public List<User> queryUsers() {
sleep(2000);
return DATAS ;
}
public User queryById(Long id) {
sleep(1000);
return DATAS.stream().filter(user -> user.getId() == id).findFirst().orElse(null) ;
}
private void sleep(int time) {
// 模擬耗時
try {
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(time)) ;
} catch (InterruptedException e) {}
}
}
Controller接口
@Monitor(tags = {"UserController", "list"})
@GetMapping("")
public List<User> list() {
return this.userService.queryUsers() ;
}
@Monitor(tags = {"UserController", "ById"})
@GetMapping("/{id}")
public User queryById(@PathVariable Long id) {
return this.userService.queryById(id) ;
}
注意:這里的注解屬性tags必須是偶數(shù),因為內(nèi)部會通過提供的字符串tag組裝成Tag對象,而Tag對象需要Key/Value。
以上是所需的所有代碼,接下來進行測試。
分別訪問上面的兩個接口。
圖片
圖片
分別多次訪問上面的接口,通過/ac/metrics/myapp.api.timer查看指標(biāo)信息。
圖片
通過Prometheus查看圖形走勢。
圖片
通過圖表方式,查看到每個接口在不同時刻的請求耗時情況。