千家信息网

如何解决SpringBoot Actuator潜在的OOM问题

发表于:2025-02-02 作者:千家信息网编辑
千家信息网最后更新 2025年02月02日,这篇文章主要介绍如何解决SpringBoot Actuator潜在的OOM问题,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!此问题背景产生于近期需要上线的一个功能的埋点;主要表
千家信息网最后更新 2025年02月02日如何解决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。

    http.client.requests 中的 uri

    塞 tag 的代码在org.springframework.boot.actuate.metrics.web.client.RestTemplateExchangeTags 类中,作用时机是在 MetricsClientHttpRequestInterceptor 拦截器中。当调用执行完成后,会将当次请求 metric 记录下来,在这里就会使用到 RestTemplateExchangeTags 来填充 tags。 下面仅给出 uri 的部分代码

      /**         * Creates a {@code uri} {@code Tag} for the URI of the given {@code request}.         * @param request the request         * @return the uri tag         */        public static Tag uri(HttpRequest request) {                return Tag.of("uri", ensureLeadingSlash(stripUri(request.getURI().toString())));        }        /**         * Creates a {@code uri} {@code Tag} from the given {@code uriTemplate}.         * @param uriTemplate the template         * @return the uri tag         */        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 的解释

       /**         * Return the URI of the request (including a query string if any,         * but only if it is well-formed for a URI representation).         * @return the URI of the request (never {@code null})         */        URI getURI();

    返回请求的 URI,这里包括了任何的查询参数。那么是不是拿到不用参数的 path 就行呢?

    这里尝试通过 request.getURI().getPath() 拿到了预期的 path(@pathvariable 拿到的是模板)。

    再回到 DefaultRestTemplateExchangeTagsProvider,所有的 tag 都是在这里完成组装,这个类明显是一个默认的实现(Spring 体系下基本只要是Defaultxxx 的,一般都能扩展 ),查看它的接口类 RestTemplateExchangeTagsProvider 如下:

    /** * Provides {@link Tag Tags} for an exchange performed by a {@link RestTemplate}. * * @author Jon Schneider * @author Andy Wilkinson * @since 2.0.0 */@FunctionalInterfacepublic interface RestTemplateExchangeTagsProvider {        /**         * Provides the tags to be associated with metrics that are recorded for the given         * {@code request} and {@code response} exchange.         * @param urlTemplate the source URl template, if available         * @param request the request         * @param response the response (may be {@code null} if the exchange failed)         * @return the tags         */        Iterable getTags(String urlTemplate, HttpRequest request, ClientHttpResponse response);}

    RestTemplateExchangeTagsProvider 的作用就是为 resttemplate 提供 tag 的,所以这里通过自定义一个 RestTemplateExchangeTagsProvider,来替换DefaultRestTemplateExchangeTagsProvider,以达到我们的目标,大致代码如下:

    @Override public Iterable 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));    }

    会不会 OOM

    理论上,应该参数不同,在使用默认 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 meterMap = new ConcurrentHashMap<>();

    一般情况下不会,这里是因为 spring boot actuator 自己提供了保护机制,对于默认情况,tags 在同一个 metric 下,最多只有 100 个

    /*** Maximum number of unique URI tag values allowed. After the max number of* tag values is reached, metrics with additional tag values are denied by* filter.*/private int maxUriTags = 100;

    如果你想使得这个数更大一些,可以通过如下配置配置

    management.metrics.web.client.max-uri-tags=10000

    如果配置值过大,会存在潜在的 oom 风险。

    以上是"如何解决SpringBoot Actuator潜在的OOM问题"这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注行业资讯频道!

    问题 参数 应用 配置 潜在 不同 代码 作用 就是 情况 数据 明显 两个 内存 内容 可以通过 实际 接口 方式 是在 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 阿里云怎么租服务器 西安大学交友软件开发 rf软件开发 计算机网络技术女生适合做什么 软件开发合同报价单 数据库和前段怎么交互 朱巍关于家庭网络安全教育 吉安个人服务器找哪家 svn服务器管理页面 远程服务器一闪登录然后黑屏 中国内最大的文献数据库是 无主之地加入哪个服务器不卡 幻想神域 服务器连接失败 计算机网络技术基础期末报告 岳阳佳伟互联网科技有限公司 互联网企业科技创新优势 哪个程序中创建数据库 网络安全事故处置应急演练 动态网页连接数据库 服务器管理平台打不开了 魔秀科技的互联网的编辑 重庆双线服务器托管口碑云主机 智能软件开发培训 合肥享充网络技术如何终止充电 html访问服务器端 文明重启怎么选爆率高的服务器 宜宾网络安全竞赛 ldap服务器连不上怎么办 海南租赁管理软件开发公司 中华武术人才数据库
    0