从一段告警日志开始
js
2025-03-28 07:45:55.798 WARN - [http-epoll-1] s.b.a.a.m.OnlyOnceLoggingDenyMeterFilter :
Reached the maximum number of URI tags for 'xxx.http.client.requests'.
Are you using 'uriVariables'?
这段告警日志表示在收集 http.client.requests
指标时,URI 标签的数量达到了最大限制。 那 http.client.requests
指标是指啥呢?
我们知道Spring Boot Actuator 通过 Micrometer 采集和报告服务程序的指标,其中接口相关的指标主要有两种:http.client.requests
和http.server.requests
,两者的区别是:
- http.client.requests指标用于监控发往外部 HTTP 服务的请求,即客户端发起的请求,适用于追踪该服务作为客户端时对其他服务(如 REST API)的调用
- http.server.requests指标用于监控接收到的 HTTP 请求,即服务器处理的请求,适用于监控该服务作为服务器时接收的请求。
可以理解为http.server.requests
是该服务应用程序提供出去的接口的监控指标,http.client.requests
是该服务应用程序调第三方接口的监控指标。
为什么会导致这段日志呢?
假设应用程序在请求一个第三方接口,接口路径是/users/{userId}
,那么当userId传不同值时http.client.requests
监控指标就会采集不同的接口请求uri,比如/users/123,/users/124,/users/124
,Grafana上的展示结果如图所示:
带来的问题
已经知道这是因为uri tag标签不规范导致采集http.client.requests
指标呈现指数级膨胀,但对于接口监控来说并不需要这么多无用的请求uri。那么会有什么样的后果呢?
- 应用程序会有潜在的OOM问题:
http.client.requests
监控指标是通过/actuator/metrics接口访问得到的,这意味这这些指标结果是存放在应用程序内存里的,在源码里是存放在map结构,如果放任不管就会导致内存中这个map结构数据越来越大,从而诱发内存OOM - 我们知道
http.client.requests
指标会被prometheus采集存放在数据库里,在Grafana通过prometheus SQL可以查询展示结果,如果prometheus数据库存放了大量这种类型的无用数据,就会拖垮prometheus数据库性能,导致Grafana查询变慢
第一个问题出现的概率比较低,因为spring boot actuator 自己提供了保护机制,对于默认情况,tags 在同一个 metric 下,最多只有 100 个,你也可以在应用程序的application.yaml文件里加上以下配置限制生成的uri tag的大小。
yaml
management:
metrics:
web:
server:
max-uri-tags: 500
但第二个问题在生产中却很常见,对于自己的应用程序应该负责规范其监控指标,prometheus是公共资源,如果不规范这是对公共资源的浪费,会给运维同事带来困扰
如何解决
这里分情况给出解决方案
情况一:不需要这个
http.client.requests
指标的话可以考虑在配置文件里禁止这个指标的生成和采集,这里有两种方法:
- 在配置文件删除或注释指标定义
js
management.metrics.web.client.request.metric-name: xxx.http.client.requests
xxx.http.client.requests是这行配置定义的,只要删掉就不会再返回
- 设置autotime为false
js
management.metrics.web.client.request.autotime.enabled=false
详细情况可以参考这个github issue,# Clarify documentation on disabling web client request metrics 注意一点的是,management.metrics.enable.http.client.requests=false
并不能禁止这个指标的生成,这点我亲自验证过了
http.client.requests 指标采用延迟初始化的方式,只有访问接口之后,才会初始化对应 uri tag 的指标。也就是说如果你没有访问接口,去 prometheus 里面查指标是查不到的。你可以通过调/actuator/metrics和/actuator/prometheus接口返回查看是否有http.client.requests
情况二:需要看到应用程序调用第三方接口的性能耗时监控,这时
http.client.requests
指标就不能禁掉了。有两种方法可以避免uri tag参数导致的指标膨胀:
- 在Webclient类里构造URI template
导致http.client.requests
的uri指标膨胀的情况有两种,一种是参数变量在path,一种是在query里,这两种情况都可以在Webclient类里构造URI template解决。
js
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
public class UserClient {
private final WebClient webClient;
public UserClient(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder.baseUrl("http://example.com/api").build();
}
// 使用路径变量
public Mono<User> getUserById(String userId) {
String uriTemplate = "/users/{userId}"; // 路径变量
return webClient.get()
.uri(uriTemplate, userId) // 传递路径变量
.retrieve()
.bodyToMono(User.class);
}
// 使用查询参数
public Mono<User> getUserByQuery(String userId) {
String uriTemplate = "/users?userId={userId}"; // 查询参数
return webClient.get()
.uri(uriTemplate, userId) // 传递查询参数
.retrieve()
.bodyToMono(User.class);
}
}
- 重写WebClientCustomizer接口
WebClientCustomizer
用于自定义 WebClient
的配置。通过实现 WebClientCustomizer
接口,你可以在创建 WebClient
实例时对其进行定制,比如添加拦截器、设置默认的请求头、配置连接池等。 通过重写 WebClientCustomizer
接口并实现自定义的 URI 替换,可以在 http.client.requests
指标中减少 URI 标签的数量