1. 程式人生 > >SpringBoot原始碼解析(八)Actuator記憶體溢位

SpringBoot原始碼解析(八)Actuator記憶體溢位

Springboot中,我們可以使用監控工具Actuator,檢視和變更spring的狀態,但是Actuator是有可能引起記憶體溢位的問題的,具體原因,分析如下:

一、Filter

在Actuator中,有一個過濾器,即MetricsWebFilter,請求監控過濾器,其filter方法如下:

	private Publisher<Void> filter(ServerWebExchange exchange, Mono<Void> call) {
		long start = System.nanoTime();
		return call.doOnSuccess((done) -> success(exchange, start))
				.doOnError((cause) -> error(exchange, start, cause));
	}

當請求完成後,無論是成功或者是失敗,其都會做相應的處理,成功:

	private void success(ServerWebExchange exchange, long start) {
		Iterable<Tag> tags = this.tagsProvider.httpRequestTags(exchange, null);
		this.registry.timer(this.metricName, tags).record(System.nanoTime() - start,
				TimeUnit.NANOSECONDS);
	}

失敗:

	private void error(ServerWebExchange exchange, long start, Throwable cause) {
		Iterable<Tag> tags = this.tagsProvider.httpRequestTags(exchange, cause);
		this.registry.timer(this.metricName, tags).record(System.nanoTime() - start,
				TimeUnit.NANOSECONDS);
	}

在success和error方法的第一行,建立了一個tags,tags是用method,uri,status等組成的一個集合。

2、Meter.Id

後面再Timer類的register方法中,tags成了Meter.Id中的一個屬性,被後續使用:

public Timer register(MeterRegistry registry) {
            // the base unit for a timer will be determined by the monitoring system implementation
            return registry.timer(new Meter.Id(name, tags, null, description, Type.TIMER), distributionConfigBuilder.build(),
                    pauseDetector == null ? registry.config().pauseDetector() : pauseDetector);
        }

在後面獲取Meter的時候,會檢視是否已經在記憶體中快取了Meter,如果存在就返回相應的Meter,如果不存在就建立Meter

    private Meter getOrCreateMeter(@Nullable DistributionStatisticConfig config,
                                   BiFunction<Id, /*Nullable Generic*/ DistributionStatisticConfig, Meter> builder,
                                   Id mappedId, Function<Meter.Id, ? extends Meter> noopBuilder) {
        Meter m = meterMap.get(mappedId);

        if (m == null) {
            if (isClosed()) {
                return noopBuilder.apply(mappedId);
            }

            synchronized (meterMapLock) {
                m = meterMap.get(mappedId);

                if (m == null) {
                    m = builder.apply(mappedId, config);

此時我們看下Meter.Id的equals方法,看下是怎麼判斷資料是否存在的,如下

return Objects.equals(name, meterId.name) && Objects.equals(tags, meterId.tags);

可以看到equals方法中,判斷了name和tags都相等,才算存在。

三、記憶體溢位

在LatencyStats類中,有許多的物件,佔用了部分記憶體空間:

    private volatile AtomicHistogram activeRecordingHistogram;
    private Histogram activePauseCorrectionsHistogram;

    private AtomicHistogram inactiveRawDataHistogram;
    private Histogram inactivePauseCorrectionsHistogram;

因為每次引數變動都會生成一個LatencyStats,由於這些LatencyStats一直存在於記憶體中,請求次數多了,便會造成記憶體溢位。

四、解決辦法

解決方法有兩個,其官網介紹如下:

Micrometer ships with a simple, in-memory backend that is automatically used as a fallback if no other registry is configured. This allows you to see what metrics are collected in the metrics endpoint.

The in-memory backend disables itself as soon as you’re using any of the other available backend. You can also disable it explicitly:

一個是禁用Metrics(Actuator)的統計在記憶體中,

management.metrics.export.simple.enabled=false

還有一個是使用別的registry。