SpringBoot Actuator潛在的OOM問(wèn)題的解決
此問(wèn)題背景產(chǎn)生于近期需要上線的一個(gè)功能的埋點(diǎn);主要表現(xiàn)就是在應(yīng)用啟動(dòng)之后的一段時(shí)間內(nèi),內(nèi)存使用一直呈現(xiàn)遞增趨勢(shì)。
下圖為場(chǎng)景復(fù)線后,本地通過(guò) jconsole 查看到的內(nèi)部使用走勢(shì)圖。
實(shí)際環(huán)境受限于配置,內(nèi)存不會(huì)膨脹
背景&問(wèn)題
應(yīng)用 a 使用 rest template 通過(guò) http 方式調(diào)用 應(yīng)用 b,應(yīng)用項(xiàng)目中開啟了 actuator,api 使用的是 micrometer;在 client 調(diào)用時(shí),actuator 會(huì)產(chǎn)生一個(gè) name 為 http.client.requests 的 metrics,此 metric 的 tag 中包含點(diǎn)目標(biāo)的 uri。
應(yīng)用 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 的收集結(jié)果中應(yīng)該包括兩個(gè) metrics,主要區(qū)別是 tag 中的 uri 不同,一個(gè)是 api/test/test_query_params, 另一個(gè)是 api/test/test_path_params/{value};實(shí)際上從拿到的 metrics 數(shù)據(jù)來(lái)看,差異很大,這里以 pathvariable 的 metric 為例,數(shù)據(jù)如下:
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} 參數(shù)作為了 uri 組件部分,并且體現(xiàn)在 tag 中,并不是期望的 api/test/test_path_params/{value}。
問(wèn)題原因及解決
兩個(gè)問(wèn)題,1、這個(gè)埋點(diǎn)是怎么生效的,先搞清楚這個(gè)問(wèn)題,才能順藤摸瓜。2、怎么解決。
默認(rèn)埋點(diǎn)是如何生效的
因?yàn)槭峭ㄟ^(guò) resttemplate 進(jìn)行調(diào)用訪問(wèn),那么埋點(diǎn)肯定也是基于對(duì) resttemplate 的代理;按照這個(gè)思路,筆者找到了 org.springframework.boot.actuate.metrics.web.client.MetricsRestTemplateCustomizer
這個(gè)類。RestTemplateCustomizer 就是對(duì) resttemplate 進(jìn)行定制的,MetricsRestTemplateCustomizer 通過(guò)名字也能得知期作用是為了給 resttemplate 增加 metric 能力。
再來(lái)討論 RestTemplateCustomizer,當(dāng)使用RestTemplateBuilder構(gòu)建RestTemplate時(shí),可以通過(guò)RestTemplateCustomizer進(jìn)行更高級(jí)的定制,所有RestTemplateCustomizer beans 將自動(dòng)添加到自動(dòng)配置的RestTemplateBuilder。也就是說(shuō)如果 想 MetricsRestTemplateCustomizer 生效,那么構(gòu)建 resttemplate 必須通過(guò) RestTemplateBuilder 方式構(gòu)建,而不是直接 new。
http.client.requests 中的 uri
塞 tag 的代碼在org.springframework.boot.actuate.metrics.web.client.RestTemplateExchangeTags
類中,作用時(shí)機(jī)是在 MetricsClientHttpRequestInterceptor 攔截器中。當(dāng)調(diào)用執(zhí)行完成后,會(huì)將當(dāng)次請(qǐng)求 metric 記錄下來(lái),在這里就會(huì)使用到 RestTemplateExchangeTags 來(lái)填充 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。
通過(guò)斷點(diǎn),可以看到,這里 request.getURI() 拿到的是帶有參數(shù)的完整請(qǐng)求鏈接。
這些 tag 的組裝最終在 DefaultRestTemplateExchangeTagsProvider 中完成,并返回一個(gè) 列表。
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"); }
解決
這里先來(lái)看下官方對(duì)于 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();
返回請(qǐng)求的 URI,這里包括了任何的查詢參數(shù)。那么是不是拿到不用參數(shù)的 path 就行呢?
這里嘗試通過(guò) request.getURI().getPath() 拿到了預(yù)期的 path(@pathvariable 拿到的是模板)。
再回到 DefaultRestTemplateExchangeTagsProvider,所有的 tag 都是在這里完成組裝,這個(gè)類明顯是一個(gè)默認(rèn)的實(shí)現(xiàn)(Spring 體系下基本只要是Defaultxxx 的,一般都能擴(kuò)展 ),查看它的接口類 RestTemplateExchangeTagsProvider 如下:
/** * Provides {@link Tag Tags} for an exchange performed by a {@link RestTemplate}. * * @author Jon Schneider * @author Andy Wilkinson * @since 2.0.0 */ @FunctionalInterface public 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<Tag> getTags(String urlTemplate, HttpRequest request, ClientHttpResponse response); }
RestTemplateExchangeTagsProvider 的作用就是為 resttemplate 提供 tag 的,所以這里通過(guò)自定義一個(gè) RestTemplateExchangeTagsProvider,來(lái)替換DefaultRestTemplateExchangeTagsProvider,以達(dá)到我們的目標(biāo),大致代碼如下:
@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)); }
會(huì)不會(huì) OOM
理論上,應(yīng)該參數(shù)不同,在使用默認(rèn) DefaultRestTemplateExchangeTagsProvider 的情況下,meter 會(huì)隨著 tags 的不同迅速膨脹,在 micrometer 中,這些數(shù)據(jù)是存在 map 中的
// Even though writes are guarded by meterMapLock, iterators across value space are supported // Hence, we use CHM to support that iteration without ConcurrentModificationException risk private final Map<Id, Meter> meterMap = new ConcurrentHashMap<>();
一般情況下不會(huì),這里是因?yàn)?spring boot actuator 自己提供了保護(hù)機(jī)制,對(duì)于默認(rèn)情況,tags 在同一個(gè) metric 下,最多只有 100 個(gè)
/** * 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;
如果你想使得這個(gè)數(shù)更大一些,可以通過(guò)如下配置配置
management.metrics.web.client.max-uri-tags=10000
如果配置值過(guò)大,會(huì)存在潛在的 oom 風(fēng)險(xiǎn)。
到此這篇關(guān)于SpringBoot Actuator潛在的OOM問(wèn)題的解決的文章就介紹到這了,更多相關(guān)SpringBoot Actuator OOM內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!?
- SpringBoot Actuator未授權(quán)訪問(wèn)漏洞修復(fù)詳解
- SpringBoot配置actuator的代碼
- SpringBoot Actuator埋點(diǎn)和監(jiān)控及簡(jiǎn)單使用
- SpringBoot實(shí)現(xiàn)監(jiān)控Actuator,關(guān)閉redis監(jiān)測(cè)
- SpringBoot配置Actuator組件,實(shí)現(xiàn)系統(tǒng)監(jiān)控
- SpringBoot基于Actuator遠(yuǎn)程關(guān)閉服務(wù)
- 深入講解SpringBoot Actuator是什么
相關(guān)文章
關(guān)于SpringMVC請(qǐng)求域?qū)ο蟮臄?shù)據(jù)共享問(wèn)題
這篇文章主要介紹了SpringMVC請(qǐng)求域?qū)ο蟮臄?shù)據(jù)共享問(wèn)題,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-02-02Java中弱引用和軟引用的區(qū)別以及虛引用和強(qiáng)引用介紹
很早Java API就添加了弱引用(WeakReference)和軟引用(SoftReference),但并不是所有的程序員都熟悉這兩個(gè)概念2014-04-04SpringMVC+Mysql實(shí)例詳解(附demo)
本篇文章主要介紹了SpringMVC+Mysql實(shí)例詳解(附demo),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。2016-12-12SpringBoot響應(yīng)處理之以Json數(shù)據(jù)返回的實(shí)現(xiàn)方法
這篇文章主要介紹了SpringBoot整合Web開發(fā)其中Json數(shù)據(jù)返回的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-09-09java file.renameTo返回false的原因及解決方案
這篇文章主要介紹了java file.renameTo返回false的原因及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07Java利用InputStream類實(shí)現(xiàn)文件讀取與處理
在Java開發(fā)中,輸入流(InputStream)是一個(gè)非常重要的概念,它涉及到文件讀寫、網(wǎng)絡(luò)傳輸?shù)榷鄠€(gè)方面,InputStream類是Java中輸入流的抽象基類,定義了讀取輸入流數(shù)據(jù)的方法,本文將以InputStream類為切入點(diǎn),介紹Java中的輸入流概念及其應(yīng)用,需要的朋友可以參考下2023-11-11