这篇文章主要介绍如何解决SpringBoot Actuator潜在的OOM问题,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!此问题背景产生于近期需要上线的一个功能的埋点;主要表现就是在应用启动之后的一段时间
这篇文章主要介绍如何解决SpringBoot Actuator潜在的OOM问题,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!
此问题背景产生于近期需要上线的一个功能的埋点;主要表现就是在应用启动之后的一段时间内,内存使用一直呈现递增趋势。
下图为场景复线后,本地通过 jconsole 查看到的内部使用走势图。
实际环境受限于配置,内存不会膨胀
应用 a 使用 rest template 通过 Http 方式调用 应用 b,应用项目中开启了 actuator,api 使用的是 micrometer;在 client 调用时,actuator 会产生一个 name 为 http.client.requests 的 metrics,此 metric 的 tag 中包含点目标的 uri。
应用 b 提供的接口大致如下:
@RequestMapping("test_query_params")public String test_query_params(@RequestParam String value) { return value;}@RequestMapping("test_path_params/{value}")public String test_path_params(@PathVariable String value) { return value;}
http://localhost:8080/api/test/test_query_params?value=
http://localhost:8080/api/test/test_path_params/{value}_
期望在 metric 的收集结果中应该包括两个 metrics,主要区别是 tag 中的 uri 不同,一个是 api/test/test_query_params, 另一个是 api/test/test_path_params/{value};实际上从拿到的 metrics 数据来看,差异很大,这里以 pathvariable 的 metric 为例,数据如下:
tag: "uri",values: ["/api/test/test_path_params/glmapper58","/api/test/test_path_params/glmapper59","/api/test/test_path_params/glmapper54","/api/test/test_path_params/glmapper55","/api/test/test_path_params/glmapper56","/api/test/test_path_params/glmapper57","/api/test/test_path_params/glmapper50","/api/test/test_path_params/glmapper51","/api/test/test_path_params/glmapper52","/api/test/test_path_params/glmapper53","/api/test/test_path_params/glmapper47","/api/test/test_path_params/glmapper48","/api/test/test_path_params/glmapper49","/api/test/test_path_params/glmapper43","/api/test/test_path_params/glmapper44","/api/test/test_path_params/glmapper45","/api/test/test_path_params/glmapper46","/api/test/test_path_params/glmapper40","/api/test/test_path_params/glmapper41","/api/test/test_path_params/glmapper42","/api/test/test_path_params/glmapper36","/api/test/test_path_params/glmapper37","/api/test/test_path_params/glmapper38","/api/test/test_path_params/glmapper39","/api/test/test_path_params/glmapper32","/api/test/test_path_params/glmapper33","/api/test/test_path_params/glmapper34","/api/test/test_path_params/glmapper35","/api/test/test_path_params/glmapper30","/api/test/test_path_params/glmapper31","/api/test/test_path_params/glmapper25","/api/test/test_path_params/glmapper26",....]
可以非常明显的看到,这里将{value} 参数作为了 uri 组件部分,并且体现在 tag 中,并不是期望的 api/test/test_path_params/{value}。
两个问题,1、这个埋点是怎么生效的,先搞清楚这个问题,才能顺藤摸瓜。2、怎么解决。
因为是通过 resttemplate 进行调用访问,那么埋点肯定也是基于对 resttemplate 的代理;按照这个思路,笔者找到了 org.springframework.boot.actuate.metrics.WEB.client.MetricsRestTemplateCustomizer
这个类。RestTemplateCustomizer 就是对 resttemplate 进行定制的,MetricsRestTemplateCustomizer 通过名字也能得知期作用是为了给 resttemplate 增加 metric 能力。
再来讨论 RestTemplateCustomizer,当使用RestTemplateBuilder构建RestTemplate时,可以通过RestTemplateCustomizer进行更高级的定制,所有RestTemplateCustomizer beans 将自动添加到自动配置的RestTemplateBuilder。也就是说如果 想 MetricsRestTemplateCustomizer 生效,那么构建 resttemplate 必须通过 RestTemplateBuilder 方式构建,而不是直接 new。
塞 tag 的代码在org.springframework.boot.actuate.metrics.web.client.RestTemplateExchangeTags
类中,作用时机是在 MetricsClientHttpRequestInterceptor 拦截器中。当调用执行完成后,会将当次请求 metric 记录下来,在这里就会使用到 RestTemplateExchangeTags 来填充 tags。 下面仅给出 uri 的部分代码
public static Tag uri(HttpRequest request) {return Tag.of("uri", ensureLeadingSlash(stripUri(request.getURI().toString())));}public static Tag uri(String uriTemplate) {String uri = (StringUtils.hasText(uriTemplate) ? uriTemplate : "none");return Tag.of("uri", ensureLeadingSlash(stripUri(uri)));
其余的还有 status 和 clientName 等 tag name。
通过断点,可以看到,这里 request.getURI() 拿到的是带有参数的完整请求链接。
这些 tag 的组装最终在 DefaultRestTemplateExchangeTagsProvider 中完成,并返回一个 列表。
private Timer.Builder getTimeBuilder(HttpRequest request, ClientHttpResponse response) { return this.autoTimer.builder(this.metricName) // tagProvider 为 DefaultRestTemplateExchangeTagsProvider.tags(this.tagProvider.getTags(urlTemplate.get().poll(), request, response)).description("Timer of RestTemplate operation");}
这里先来看下官方对于 request.getURI 的解释
URI getURI();
返回请求的 URI,这里包括了任何的查询参数。那么是不是拿到不用参数的 path 就行呢?
这里尝试通过 request.getURI().getPath() 拿到了预期的 path(@pathvariable 拿到的是模板)。
再回到 DefaultRestTemplateExchangeTagsProvider,所有的 tag 都是在这里完成组装,这个类明显是一个默认的实现(Spring 体系下基本只要是Defaultxxx 的,一般都能扩展 ),查看它的接口类 RestTemplateExchangeTagsProvider 如下:
@FunctionalInterfacepublic interface RestTemplateExchangeTagsProvider {Iterable<Tag> getTags(String urlTemplate, HttpRequest request, ClientHttpResponse response);}
RestTemplateExchangeTagsProvider 的作用就是为 resttemplate 提供 tag 的,所以这里通过自定义一个 RestTemplateExchangeTagsProvider,来替换DefaultRestTemplateExchangeTagsProvider,以达到我们的目标,大致代码如下:
@Override public Iterable<Tag> getTags(String urlTemplate, HttpRequest request, ClientHttpResponse response) { Tag uriTag; // 取 request.getURI().getPath() 作为 uri 的 value if (StringUtils.hasText(request.getURI().getPath())) { uriTag = Tag.of("uri", ensureLeadingSlash(stripUri(request.getURI().getPath()))); } else { uriTag = (StringUtils.hasText(urlTemplate) ? RestTemplateExchangeTags.uri(urlTemplate) : RestTemplateExchangeTags.uri(request)); } return Arrays.asList(RestTemplateExchangeTags.method(request), uriTag, RestTemplateExchangeTags.status(response), RestTemplateExchangeTags.clientName(request)); }
理论上,应该参数不同,在使用默认 DefaultRestTemplateExchangeTagsProvider 的情况下,meter 会随着 tags 的不同迅速膨胀,在 micrometer 中,这些数据是存在 map 中的
// Even though writes are guarded by meterMapLock, iterators across value space are supported// Hence, we use CHM to support that iteration without ConcurrentModificationException riskprivate final Map<Id, Meter> meterMap = new ConcurrentHashMap<>();
一般情况下不会,这里是因为 Spring Boot actuator 自己提供了保护机制,对于默认情况,tags 在同一个 metric 下,最多只有 100 个
private int maxUriTags = 100;
如果你想使得这个数更大一些,可以通过如下配置配置
management.metrics.web.client.max-uri-tags=10000
如果配置值过大,会存在潜在的 oom 风险。
以上是“如何解决SpringBoot Actuator潜在的OOM问题”这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注编程网精选频道!
--结束END--
本文标题: 如何解决SpringBoot Actuator潜在的OOM问题
本文链接: https://www.lsjlt.com/news/300900.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
下载Word文档到电脑,方便收藏和打印~
2024-05-24
2024-05-24
2024-05-24
2024-05-24
2024-05-24
2024-05-24
2024-05-24
2024-05-24
2024-05-24
2024-05-24
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
一口价域名售卖能注册吗?域名是网站的标识,简短且易于记忆,为在线用户提供了访问我们网站的简单路径。一口价是在域名交易中一种常见的模式,而这种通常是针对已经被注册的域名转售给其他人的一种方式。
一口价域名买卖的过程通常包括以下几个步骤:
1.寻找:买家需要在域名售卖平台上找到心仪的一口价域名。平台通常会为每个可售的域名提供详细的描述,包括价格、年龄、流
443px" 443px) https://www.west.cn/docs/wp-content/uploads/2024/04/SEO图片294.jpg https://www.west.cn/docs/wp-content/uploads/2024/04/SEO图片294-768x413.jpg 域名售卖 域名一口价售卖 游戏音频 赋值/切片 框架优势 评估指南 项目规模
0