中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何解決SpringBoot?Actuator潛在的OOM問題

發布時間:2021-11-30 12:56:51 來源:億速云 閱讀:254 作者:小新 欄目:開發技術

這篇文章主要介紹如何解決SpringBoot Actuator潛在的OOM問題,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!

    此問題背景產生于近期需要上線的一個功能的埋點;主要表現就是在應用啟動之后的一段時間內,內存使用一直呈現遞增趨勢。

    下圖為場景復線后,本地通過 jconsole 查看到的內部使用走勢圖。

    如何解決SpringBoot?Actuator潛在的OOM問題

    實際環境受限于配置,內存不會膨脹

    背景&問題

    應用 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() 拿到的是帶有參數的完整請求鏈接。

    如何解決SpringBoot?Actuator潛在的OOM問題

    這些 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 就行呢?

    如何解決SpringBoot?Actuator潛在的OOM問題

    這里嘗試通過 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
     */
    @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 的,所以這里通過自定義一個 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));
        }

    會不會 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 risk
    private final Map<Id, Meter> 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問題”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    公安县| 铜梁县| 营山县| 辰溪县| 疏附县| 沾益县| 读书| 金华市| 咸丰县| 陇南市| 巴林右旗| 徐汇区| 怀来县| 余庆县| 凉城县| 阿克陶县| 青岛市| 靖州| 安义县| 长治市| 贞丰县| 墨脱县| 沛县| 南召县| 博爱县| 仲巴县| 博乐市| 南汇区| 达拉特旗| 海阳市| 松原市| 磐安县| 泰顺县| 讷河市| 禄劝| 韩城市| 开封县| 宣城市| 静安区| 安塞县| 昆山市|