精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Prometheus 監控實踐:Java 開發者如何通過 PromQL 和 Grafana 優化監控策略

開發
本文通過spring boot web項目作為演示案例,所有的采集指標都會通過Prometheus數據源發布到rgafana上并通過promQL進行增強渲染,所以該項目主要會引入暴露springboot監控指標進行和prometheus套件依賴

因為近期工作比較忙碌,所以文章的更新相對慢了一些,近期筆者集成了一些比較核心的監控指標交由Prometheus采集,并通過promQL進行查詢分析實現圖表渲染。因為這一套完整的監控流程涉及計量器指標采集再通過Prometheus構建時間序列,再通過grafana結合promQL查詢渲染,所以了解每一個環節的實現和理念,才能準確串聯上述流程。

遺憾的是,就筆者近期了解的情況來看,這方面的資料要么面向全流程搭建的新手教程,要么就是非常突兀的promQL基本說明,并沒有做到筆者所認為的全流程泛化梳理,所以筆者打算綜合這些理念,結合一個比較有代表意義的案例將這些概念串聯,以幫助讀者更好的理念和運用監控。

一、案例項目前置說明

本文通過spring boot web項目作為演示案例,所有的采集指標都會通過Prometheus數據源發布到rgafana上并通過promQL進行增強渲染,所以該項目主要會引入暴露springboot監控指標進行和prometheus套件依賴:

<!--暴露spring監控指標-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
            <version>2.4.1</version>
        </dependency>

        <!--用于導出prometheus系統類型的指標數據-->
        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-registry-prometheus</artifactId>
            <version>1.1.4</version>
        </dependency>

同時筆者也變寫了一個測試的TestController:

  • 聲明計時器采集test0的耗時(本質上通過休眠模擬)指標
  • 其余兩個接口通過@Timed注解收集接口時間維度的各項指標
@RestController
@Slf4j
public class TestController {

    @Autowired
    private MeterRegistry registry;
    private Timer timer;


    @PostConstruct
    private void init() {
        //名稱設置為http.timer,標簽設置為uri為/hello,選用合適的名稱輔助開發推斷理解
        timer = Timer
                .builder("http.timer")
                .publishPercentiles(0.1, 0.5, 0.95) //發布百分位數區間
                .description("接口請求耗時統計") // 指標的描述
                .tags("uri", "/hello") // url標簽指明為hello
                .register(registry);


    }


    @GetMapping("/test0")
    public String function() {
        timer.record(RandomUtil.randomInt(200), TimeUnit.MILLISECONDS);
        return "test0";
    }

    @GetMapping("/test1")
    @Timed
    public String test1() {
        ThreadUtil.sleep(RandomUtil.randomInt(200));
        return "test1";
    }

    @GetMapping("/test2")
    @Timed
    public String test2() {

        ThreadUtil.sleep(RandomUtil.randomInt(200));
        return "test2";
    }

}

因為用到的計時器注解 @Timed,所以我們還需要配置TimedAspect創建注解的代理是使之生效:

@Configuration
public class TimedConfiguration {
    @Bean
    public TimedAspect timedAspect(MeterRegistry registry) {
        return new TimedAspect(registry);
    }
}

這里筆者也簡單普及一下TimedAspect這個切面的工作原理,在springboot進行自動裝配的時候掃描到TimedAspect,該切面會針對所有所有帶有Timed注解的bean的方法做一個環繞增強,在連接點前后記錄耗時并通過Timer記錄耗時到計時器中:

對應的TimedAspect的切點實現timedMethod如下:

@Around("execution (@io.micrometer.core.annotation.Timed * *.*(..))")
    public Object timedMethod(ProceedingJoinPoint pjp) throws Throwable {
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        Timed timed = method.getAnnotation(Timed.class);
        //通過注解獲取方法的元信息
        if (timed == null) {
            method = pjp.getTarget().getClass().getMethod(method.getName(), method.getParameterTypes());
            timed = method.getAnnotation(Timed.class);
        }

        final String metricName = timed.value().isEmpty() ? DEFAULT_METRIC_NAME : timed.value();
        //啟動計時器
        Timer.Sample sample = Timer.start(registry);
        String exceptionClass = "none";
    
        try {
        //調用方法
            return pjp.proceed();
        } catch (Exception ex) {
          //......
        } finally {
            try {
       //記錄方法耗時
                sample.stop(Timer.builder(metricName)
                        .description(timed.description().isEmpty() ? null : timed.description())
                        .tags(timed.extraTags())
                        .tags(EXCEPTION_TAG, exceptionClass)
                        .tags(tagsBasedOnJoinPoint.apply(pjp))
                        .publishPercentileHistogram(timed.histogram())
                        .publishPercentiles(timed.percentiles().length == 0 ? null : timed.percentiles())
                        .register(registry));
            } catch (Exception e) {
                // ignoring on purpose
            }
        }

最后就是指標暴露和端口發布的配置:

server.port=8080
spring.application.name=web-service

# 暴露并開啟所有的端點,Spring Boot Actuator會自動配置一個 URL 為 /actuator/Prometheus 的 HTTP 服務來供 Prometheus 抓取數據
management.endpoints.web.exposure.include=*
# 展示所有的健康信息
management.endpoint.health.show-details=always
# 默認/actuator/Prometheus,添加這個tag方便區分不同的工程
management.metrics.tags.applicatinotallow=${spring.application.name}
# Actuator 監控端點獨立端口設置為 18080(與主應用端口分離)
management.server.port=18080

二、詳解各大計量器工作原理

1. counter(計數器)

(1) 應用場景

counter從名字即可了解這個計量器本質上是一個只增不減的計數器,它是有狀態的(即依賴于歷史的值),從使用方法上來看,它是單調遞增的且上界是不可確定的,所以使用counter進行監控的指標一般是需要存在不斷累加且需要針對累加的趨勢進行分析的。

最典型的場景就是接口請求總數,例如我們需要針對上述的test1接口請求進行計數,從而構成時間序列存儲這些數據,同時針對單位時間內這個接口增量趨勢進行分析,主流的做法就是通過counter采集每一次請求,并將該指標通過prometheus交給grafana通過promQL進行即席查詢分析:

(2) 使用示例

針對counter計數器的核心本質,即只要做到針對并發請求進行高效計數即可,其余一些分析維度的工作全部交由prometheus等數據源進行定期的采集分析即可,對應筆者項目中的應用方式就如下這個環繞切面的代碼段:

  • 攔截所有帶有http注解的接口
  • 拉取該接口方法名并生成標簽
  • 針對該url的counter進行累加
@Around("execution(@org.springframework.web.bind.annotation.GetMapping * *(..)) || " +
            "execution(@org.springframework.web.bind.annotation.PostMapping * *(..)) || " +
            "execution(@org.springframework.web.bind.annotation.RequestMapping * *(..))")
    public Object countHttpRequest(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        //拉取接口方法名
        String url = extractUrl(method);
        //針對url創建切面
        Counter counter = Counter.builder("http_requests_total")
                .tag("url", url)
                .register(meterRegistry);
        //針對接口請求數進行累加        
        counter.increment();
      

        return joinPoint.proceed();
    }

(3) 核心原理

所以其實現的核心主要還是強調計數的準確性和高效性,考慮到counter在并發場景下更多是針對計數進行累加,且只有在grafana等監控系統查詢時才需要獲取計數值,所以針對這種寫多讀少且需要保證并發安全的場景。

所以counter底層采用了基于數組分散并發累加壓力的計數器DoubleAdder:

對此我們可以查看counter底層的源碼實現即PrometheusCounter的increment印證這一點:

public class PrometheusCounter extends AbstractMeter implements Counter {
    private DoubleAdder count = new DoubleAdder();

   //......

    @Override
    public void increment(double amount) {
        if (amount > 0)
          //通過DoubleAdder完成并發累加
            count.add(amount);
    }

}

2. guage(儀表類型)

(1) 應用場景

guage也是我們常用的儀表盤,和counter有所不同,guage計數器可以增減,它是無狀態的,即此刻的數值與歷史數值并沒有依賴關系,它更常用于觀察帶有上下界的指標,即側重于那些系統狀態的指標:

  • cpu利用率
  • 內存利用率
  • 網絡帶寬

所以針對gauge的使用理念,我們也還是通過gauge采集單位時間下的指標的數值,然后交給grafana讓其通過promQL分析其增減趨勢亦或者上下浮動情況以準確針對系統情況進行深入分析:

(2) 使用示例

類似的我們通過spring boot的registry全局注冊一個guage,通過原子類進行設置值,即直接通過原子類記錄當前cpu使用率,讓prometheus定期采集交給grafana進行即席查詢分析:

AtomicInteger cost = registry.gauge("cpu.usage", Tags.of("core-number", "0"), new AtomicInteger(0));
        //隨機數模擬cpu使用率
        cost.set(RandomUtil.randomInt(100));

(3) 工作原理

我們注冊gauge的時候通過構造函數指明底層采用AtomicInteger進行數值維護,后續我們就可以直接操作這個原子類引用完成數值維護修改:

AtomicInteger cost = registry.gauge("cpu.usage", Tags.of("core-number", "0"), new AtomicInteger(0));

為什么可以采用原子類AtomicInteger呢?查看gauge底層實現,從源碼可以看到該參數為泛型T只需繼承Number類,保證獲取數值時可以通過doubleValue方法返回值即可。

而AtomicInteger恰好繼承Number類且保證并發計數安全,所以才適用于作為gauge底層的計數器:

@Nullable
    public <T extends Number> T gauge(String name, Iterable<Tag> tags, T number) {
  
        return gauge(name, tags, number, Number::doubleValue);
    }

結合這個泛型構造,我們也可以直接采用LongAdder作為gauge底層的計量器,由于其底層采用數組三列并發累加壓力,所以更適用于作為監控計數的指標:

LongAdder gauge = registry.gauge("cpu.usage", Tags.of("core-number", "0"), new LongAdder());

3. timer

(1) 應用場景

timer計時器主要是跟蹤大量短耗時的事件進行多維度的采集,通過計時器統計某個事件耗時時,其底層會維護針對此事件:

  • 事件總數:采用LongAdder維護
  • 事件總耗時:同樣采用LongAdder維護
  • 事件耗時最大值:通過TimeWindowMax時間窗口進行維護

后續我們就可以通過prometheus構成時間序列將其交給grafana,此時我們就可以根據這些指標計算:

  • 當前一段時間請求總數
  • 當前一段時間的平均耗時
  • 當前一段時間的最大值

(2) 使用示例

對應的用法上文已經介紹過,我們可以自定義注冊一個timer,后續直接用這個timer的record方法記錄耗時:

@Autowired
    private MeterRegistry registry;
    private Timer timer;


    @PostConstruct
    private void init() {
        //名稱設置為http.timer,標簽設置為uri為/hello,選用合適的名稱輔助開發推斷理解
        timer = Timer
                .builder("http.timer")
                .publishPercentiles(0.5, 0.95) //發布百分位數區間
                .description("接口請求耗時統計") // 指標的描述
                .tags("url", "/test0") // url標簽指明為hello
                .register(registry);


    }

耗時記錄使用示例如下:

@GetMapping("/test0")
    public String function() {
        int sleepTime = RandomUtil.randomInt(200);
        ThreadUtil.sleep(sleepTime);
        timer.record(sleepTime, TimeUnit.MILLISECONDS);
        return "test0";
    }

(3) 工作原理

關于timer針對上述三個度量指標,從上文表述我們就知道大體就是通過:

  • count記錄請求總數
  • totalTime記錄總耗時
  • max窗口工具類維護最大耗時

對應的我們也可以通過timer底層實現PrometheusTimer印證這一點:

public class PrometheusTimer extends AbstractTimer {
  //......
  //記錄請求總數
    private final LongAdder count = new LongAdder();
    //記錄總耗時
    private final LongAdder totalTime = new LongAdder();
    //窗口內記錄最大耗時
    private final TimeWindowMax max;

 //......
}

當我們的通過timer記錄本次接口耗時,record方法本質做的是:

  • count原子自增請求總數
  • totalTime原子累加記錄總耗時
  • max通過一個環形緩沖區維護1min以內請求的最大值

對應第一點和第二點都是簡單的原子累加操作,這里就不多做贅述了,我們著重的說明一下最大耗時這個操作的底層工作原理,這個記錄最大值的工具類TimeWindowMax本質上是用一個喚醒緩沖區實現(本質上就是一個數組),數組3個元素分別代表:

  • 當前1min內的最大值
  • 當前2min內的最大值
  • 當前3min內的最大值:

我們都知道這個max計數器記錄的都是當前1min內耗時最大的值,假設我們當前這分鐘的最大值為200ms,那么ringbuffer[0]記錄的最大值就是200ms。 注意:TimeWindowMax維護最大值是會遍歷數組中每個元素進行比對,然后將最大值寫入:

一旦ringbuffer[0]使用時間超過1min,例如當前時間是3:50距離ringbuffer[0]使用開始時間1:00已經超過170s,TimeWindowMax就會執行如下步驟:

  • 計算時間差為170,已經超過ringbuffer[0]的窗口區間(當前1min內的最大值),所以將該原子類重置為0,指針移動到ringbuffer[1]
  • ringbuffer[1]代表當前2min內的最大值,170s也大于其窗口時間區間120s,所以這個窗口也過期直接重置為0,指針移動到ringbuffer[1]
  • ringbuffer[2]代表當前3min內的數組,對應窗口活躍保質期為180s大于170s,所以沒過期

所以ringbuffer[2]這個窗口后續作為當前1min內的窗口,其他窗口循環重置后循環復用作為當前2min、3min內的窗口,這就是這個算法的巧妙所在:

對應的我們也可以通過源碼印證這一點,可以看到timer底層調用record入口來自AbstractTimer,這個抽象類對外暴露recordNonNegative這個抽象方法,對應也就是我們的工具類PrometheusTimer的recordNonNegative方法:

@Override
    public final void record(long amount, TimeUnit unit) {
        if (amount >= 0) {
            //......
            //記錄請求總數、耗時、最大值
            recordNonNegative(amount, unit);

            //......
        }
    }

查看PrometheusTimer的recordNonNegative可以發現他做了如下三件事:

  • counter自增維護請求總數
  • totalTime累加計算總耗時
  • max.record記錄最大耗時
@Override
    protected void recordNonNegative(long amount, TimeUnit unit) {
       //累計請求總數
        count.increment();
        long nanoAmount = TimeUnit.NANOSECONDS.convert(amount, unit);
        //累加總耗時
        totalTime.add(nanoAmount);
        //維護最大值
        max.record(nanoAmount, TimeUnit.NANOSECONDS);

       //......
    }

關于請求和耗時累計邏輯比較直觀,筆者就不多做介紹了,步入max的record就可以看到核心所在:

  • 調用rotate執行我們上述圖解的窗口滑動算法整理三個窗口
  • 基于必要整理重置后的窗口數組和當前耗時進行比對,維護最新的最大值
public void record(double sample, TimeUnit timeUnit) {
   //窗口旋轉維護
        rotate();
        //遍歷各個緩沖區并維護最大值
        final long sampleNanos = (long) TimeUtils.convert(sample, timeUnit, TimeUnit.NANOSECONDS);
        for (AtomicLong max : ringBuffer) {
            updateMax(max, sampleNanos);
        }
    }

查看rotate源碼中可以看到,rotate就是實現窗口旋轉的核心,其內部做了如下幾件事:

  • 它會獲取當前時間距離上次窗口旋轉時間,判斷是否超期,若超過60s則說明存在過期窗口需要滑動窗口,進入步驟2
  • cas上鎖保證只有一個線程執行此操作
  • 遍歷各個元素,通過距離上次旋轉時間timeSinceLastRotateMillis不斷循環減去60s和durationBetweenRotatesMillis比較以做到
1. 第1次循環減去0個60,即查看第一個窗口得到的timeSinceLastRotateMillis是否超過60,若超過則說明過期
2. 第2次循環減去1個60,即查看第2個窗口得到的timeSinceLastRotateMillis-60s是否超過60(即是否超過2min),若超過則說明過期
3. ......

完成窗口重置和滑動后,將本次的耗時分別于各個窗口進行比對,如果比窗口值大則直接寫入,這個算法比較巧妙,讀者可以結合筆者的說明自行理解:

private void rotate() {
   //計算上次旋轉窗口的時間
        long timeSinceLastRotateMillis = clock.wallTime() - lastRotateTimestampMillis;
        //如果沒有超過60s則返回
        if (timeSinceLastRotateMillis < durationBetweenRotatesMillis) {
            // Need to wait more for next rotation.
            return;
        }
    //上個自旋鎖保證并發互斥,進行窗口滑動操作
        if (!rotatingUpdater.compareAndSet(this, 0, 1)) {
            // Being rotated by other thread already.
            return;
        }

        try {
            int iterations = 0;
            synchronized (this) {
                do {
                 //重置當前窗口
                    ringBuffer[currentBucket].set(0);
                    //移動到下一個窗口,如果超過上界則回到索引0位置
                    if (++currentBucket >= ringBuffer.length) {
                        currentBucket = 0;
                    }
                    //減去60s查看這個窗口是否超過區間,因為是do while循環,所以多次循環就可以做到查看60s、120s(循環1次減去一個60和60進行比對)、180s(循環2次減去2個60和60進行比對)對應的3個緩沖區是否過期
                    timeSinceLastRotateMillis -= durationBetweenRotatesMillis;
                    //上次滑動窗口時間加上60s,即代表這個窗口區間理論上的旋轉窗口時間
                    lastRotateTimestampMillis += durationBetweenRotatesMillis;
                    //
                } while (timeSinceLastRotateMillis >= durationBetweenRotatesMillis && ++iterations < ringBuffer.length);
            }
        } finally {
            rotating = 0;
        }
    }

三、詳解promQL

1. promQL 指標的基本構成說明

在正式介紹promQL表達式之前,我們需要先針對Prometheus風格的指標構成進行一下必要的對齊:

  • #號部分為必要的描述和注釋說明,如下注釋分別對應我們自定義的指標描述和Prometheus的計量器說明(本例則是guage)
  • jvm_threads_states_threads為指標名稱
  • 后續{}部分則是針對jvm_threads_states_threads各個不同維度區分的標簽
# HELP jvm_threads_states_threads The current number of threads having NEW state
# TYPE jvm_threads_states_threads gauge
jvm_threads_states_threads{applicatinotallow="web-service",state="blocked",} 0.0
jvm_threads_states_threads{applicatinotallow="web-service",state="waiting",} 23.0
jvm_threads_states_threads{applicatinotallow="web-service",state="runnable",} 11.0
jvm_threads_states_threads{applicatinotallow="web-service",state="timed-waiting",} 4.0
jvm_threads_states_threads{applicatinotallow="web-service",state="new",} 0.0
jvm_threads_states_threads{applicatinotallow="web-service",state="terminated",} 0.0

我們以jvm_threads_states_threads{applicatinotallow="web-service",state="blocked",} 0.0為例說明一下,這是一個典型的Prometheus風格的指標值,通過標簽名限定當前指標的語義,例如jvm_threads_states_threads就代表不同狀態的線程數,同時通過標簽聲明指標的維度,以該指標為例,則是通過應用名稱application和狀態state區分單指標下不同維度的數值,最后就是指標的數值:

2. promQL常見表達式

(1) promQL核心概念

瞬時向量(Instant vector):一組時間序列上,每個時間上只有一個樣本,他們共享相同的時間戳,即表達式的返回值只會包含該時間中的最新的樣本值:

區間向量(Range vector):即一個時間范圍內的每個時間序列包含一段時間范圍內的樣本數據:

時間向量:以時間為橫坐標,序列作為縱坐標構成一組反應狀態變化的向量圖,該向量圖通過定時周期性采集,隨著時間的流逝生成一個離散的樣本數據序列。 通過指標名稱結合標簽生成多條趨勢線條,也就是多條時間序列,而序列也就是我們常說的vector:

(2) 匹配表達式

有了上述對于計量器的基本介紹,我們在針對promQL中幾個比較常見的表達式和函數展開介紹,promQL中也存在著邏輯表達式,這其中涉及匹配表達式和邏輯表達式。

我們先來說說匹配表達式:

  • 完全匹配:與字符串完全匹配即=
  • 不匹配:與字符串不匹配即!=
  • 正則匹配:與字符串正則匹配=~
  • 正則反向過濾:與字符串正則不匹配!~

它可以針對多維度的指標進行篩選和檢索,例如我們從spring actuator上看到jvm線程各個狀態的指標及其對應的線程數:

# HELP jvm_threads_states_threads The current number of threads having NEW state
# TYPE jvm_threads_states_threads gauge
jvm_threads_states_threads{applicatinotallow="web-service",state="blocked",} 0.0
jvm_threads_states_threads{applicatinotallow="web-service",state="waiting",} 23.0
jvm_threads_states_threads{applicatinotallow="web-service",state="runnable",} 11.0
jvm_threads_states_threads{applicatinotallow="web-service",state="timed-waiting",} 4.0
jvm_threads_states_threads{applicatinotallow="web-service",state="new",} 0.0
jvm_threads_states_threads{applicatinotallow="web-service",state="terminated",} 0.0

默認情況下,它在Prometheus的console渲染顯示如下:

如果我們希望只希望查看timed-waiting的線程數,此時我們就可以通過標簽結合相等匹配器實現,對應的表達式為:

jvm_threads_states_threads{state="timed-waiting"}

此時視圖就會準確過濾篩選出狀態為timed-waiting的線程數:

同理過濾出狀態非timed-waiting的表達式為:

jvm_threads_states_threads{state!="timed-waiting"}

同理,如果我們希望匹配r開頭的表達式則是:

jvm_threads_states_threads{state=~"r.*"}

(3) 邏輯表達式

promQL也存在和各種邏輯運算的表達式匹配:

  • and:即兩個序列即上述的vector進行與運算產生新的集合,只有兩個即可都存在的元素才會顯示
  • or:只要左右任何一邊的vector表達式計算為真,就顯示左右vector的所有元素
  • unless:即左右兩邊的vector進行或運算構成新的并集,然后通過unless右邊的vector進行過濾,將右邊vector存在的元素移除

對此我們不妨距離說明,關于邏輯表達式我們以一個針對http請求數計算的指標http_requests_total為例進行演示,對應指標如下:

# HELP http_requests_total  
# TYPE http_requests_total counter
http_requests_total{applicatinotallow="web-service",url="/test2",} 1.0
http_requests_total{applicatinotallow="web-service",url="/test0",} 2.0
http_requests_total{applicatinotallow="web-service",url="/test1",} 340.0

如果我們希望查詢請求數大于300且映射以test開頭,在promQL表達式則是采用and,對應的表達式如下,最終的輸出結果也是:

http_requests_total > 300 and http_requests_total{url=~"/test.*"}

最終輸出的也是test1:

同理如果希望查詢請求數大于300或者映射為test0,則表達式如下:

http_requests_total > 300 or http_requests_total{url="/test0"}

需要注意的是promQL表達式中的or并非短路運算,即表達式為真的情況下,左右vector都會輸出,也就是大于請求數大于300和test0映射都會輸出:

最后則是unless,相較于常規的邏輯表達式,該邏輯表達式的執行邏輯為將左右或運算得到交集后,結果交由右邊過濾得出目標標簽數據,例如我們需要查詢出請求總數大于0但要排除test0,對應的表達式就如下所示:

http_requests_total > 0 unless http_requests_total{url="/test0"}

對應的推算過程為:

  • 將請求數大于0和/test0的指標通過或運算構成新集合即/test0、/test1、/test2
  • 基于右邊vector將非test0的元素過濾,最終得到/test1和/test2:

3. 常見函數

(1) 聚合函數

接下來就是介紹一些比較常見的函數,和常見的sql語句一樣,promQL也有如下常見內置函數:

  • sum:指標求和
  • avg:指標平均數
  • max:指標最大值
  • min:指標最小值

我們還是以http_requests_total為例,對應不同接口的請求總數如下:

# HELP http_requests_total  
# TYPE http_requests_total counter
http_requests_total{applicatinotallow="web-service",url="/test2",} 1.0
http_requests_total{applicatinotallow="web-service",url="/test0",} 2.0
http_requests_total{applicatinotallow="web-service",url="/test1",} 340.0

假設我們希望定位出http_requests_total的最大值,對應的就可以使用max(http_requests_total),其余函數同理,這些函數本質上就是基于當前指標通過函數聚合計算,比較簡單筆者就不多做演示了。

(2) 時間樣本分析常用函數

對于監控來說,我們更希望看到監控指標的整體趨勢,觀察系統的動態以便進行針對性的調優,這其中常見的函數有:

  • max_over_time:指定一段時間的最大值
  • avg_over_time:指定一段時間的平均值
  • min_over_time:指定一段時間的最小值
  • rate:計算指定時間范圍內平均每秒增長率
  • delta:觀察系統一段時間指標上下浮動差

假設我們通過timer維護一份基于時間維度的各個接口耗時、請求總數、最大值等信息:

# HELP method_timed_seconds  
# TYPE method_timed_seconds summary
method_timed_seconds_count{applicatinotallow="web-service",class="com.sharkchili.controller.TestController",exceptinotallow="none",method="test2",} 1.0
method_timed_seconds_sum{applicatinotallow="web-service",class="com.sharkchili.controller.TestController",exceptinotallow="none",method="test2",} 0.032457149
method_timed_seconds_count{applicatinotallow="web-service",class="com.sharkchili.controller.TestController",exceptinotallow="none",method="test1",} 840.0
method_timed_seconds_sum{applicatinotallow="web-service",class="com.sharkchili.controller.TestController",exceptinotallow="none",method="test1",} 84.248322878
# HELP method_timed_seconds_max  
# TYPE method_timed_seconds_max gauge
method_timed_seconds_max{applicatinotallow="web-service",class="com.sharkchili.controller.TestController",exceptinotallow="none",method="test2",} 0.0
method_timed_seconds_max{applicatinotallow="web-service",class="com.sharkchili.controller.TestController",exceptinotallow="none",method="test1",} 0.198215155

若我們希望查看過去1h耗時的最大值分布,對應表達式為 max_over_time(method_timed_seconds_max[1h]),對應輸出結果如下,其余平均值、最小值也是同理。

這其中還有用一個針對指標整體浮動變化的函數delta,例如我們有一個cpu的guage指標:

# HELP system_cpu_usage The "recent cpu usage" for the whole system
# TYPE system_cpu_usage gauge
system_cpu_usage{applicatinotallow="web-service",} 0.005636978579481398

如果我們希望通過cpu浮動情況判斷程序資源消耗穩定性就可以通過delta即delta(system_cpu_usage[2h])檢測過去2h的cpu浮動變化:

我們在介紹一下比較實用的函數,針對請求接口總數這種單向攀升的指標,我們也會關注它的增長趨勢已判斷服務器整體資源是否符合未來增長趨勢,我們就可以通過rate函數來分析如下接口請求總數指標:

# HELP http_requests_total  
# TYPE http_requests_total counter
http_requests_total{applicatinotallow="web-service",url="/test2",} 1.0
http_requests_total{applicatinotallow="web-service",url="/test0",} 2.0
http_requests_total{applicatinotallow="web-service",url="/test1",} 840.0

對應表達式為rate(http_requests_total[1h]),對應輸出發布圖如下,我們可以非常直觀的看到test1接口在單位時間內瘋狂的攀升:

四、實踐——promQL與grafana的串聯

1. 長尾問題說明

監控的目的本質上是針對指標的趨勢分析確保能夠對系統有一個準確的決策優化思路,這其中就有一個比較經典的長尾問題,以我們監控接口耗時為例,1min內平均耗時為200ms,但是偶發出現5s,這種偶發波動對于rate等函數進行平均化之后就會被削平,從而無法及時的發現偶發飆升的數值進而無法及時發現問題,這種情況也就是長尾問題。

對于此類問題,我們就需要綜合指標多維度針對指標進行圖表分析,從而進行準確的進一步決策。我們還是以接口請求總數的指標為例,假設此時我們收到接口的請求總數counter情況如下,可以看到有大量請求打到test1上,所以test1的請求總數為910:

# HELP http_requests_total  
# TYPE http_requests_total counter
http_requests_total{applicatinotallow="web-service",url="/test2",} 1.0
http_requests_total{applicatinotallow="web-service",url="/test0",} 2.0
http_requests_total{applicatinotallow="web-service",url="/test1",} 910.0

針對這些接口,筆者也通過timer計時器針對性的進行指標采集,還是以test1說明:

  • 請求總數為909(采集時間和上述有些誤差)
  • 請求總耗時為91s
  • 最大耗時為199ms
# HELP method_timed_seconds  
# TYPE method_timed_seconds summary
# ......
method_timed_seconds_count{applicatinotallow="web-service",class="com.sharkchili.controller.TestController",exceptinotallow="none",method="test1",} 909.0
method_timed_seconds_sum{applicatinotallow="web-service",class="com.sharkchili.controller.TestController",exceptinotallow="none",method="test1",} 91.202510617



# HELP method_timed_seconds_max  
# TYPE method_timed_seconds_max gauge
# ......
method_timed_seconds_max{applicatinotallow="web-service",class="com.sharkchili.controller.TestController",exceptinotallow="none",method="test1",} 0.199230275

對應的我們將http_requests_total寫入粘貼到grafana上渲染后如下圖所示:

2. 基于計數器分析http請求增量情況

我們先通過http_requests_total對接口請求情況進行分析,從整體情況來看test1請求在不斷的飆升,所以我們希望針對該接口增量趨勢進行分析,于是鍵入rate(http_requests_total{url="/test1"}[1m])分析了test1接口的增長情況。 可以看到整體是一段時間一段時間的波動,按照實際業務場景可以是服務定時任務在單位時間內的feign請求:

3. 基于timer計時器分析接口耗時

看到此波動,就需要關心這個接口的耗時情況,通過timer計時器的指標(method_timed_seconds_)篩選業務峰值的時間區間真是這種飆升的量級請求的各維度耗時進行匯總分析,以確定的接口飆升是否存在瓶頸。

首先我們需要查詢接口最大耗時max_over_time(method_timed_seconds_sum[1h])查看過去1h的最大耗時,整體來看基本穩定在200ms以內,符合團隊指定的標準:

明確沒有存在瓶頸的情況下,我們也需要判斷接口單位時間內的平均耗時已確定系統過去一段時間是否穩定運行,已確定程序或者系統是否存在波動,已明確是否有隱患,表達式為method_timed_seconds_sum{method="test1"}/method_timed_seconds_count{method="test1"},結合上述指標來看在00:10那一刻請求數飆升所以那段時間平均耗時增加,請求降下來后耗時也將下來了:

4. 基于gauge分析CPU負載情況

最后我們還是需要通過分析一下cpu和內存使用情況已確定這種飆升對于系統的壓力如何,我們直接通過max_over_time(system_cpu_usage[1h])查看最大開始也就是3%并沒有超過業界認定的瓶頸70%,基本確定沒有問題:

關于內存筆者這里也直接關聯到jvm_memory_used_bytes這個guage,通過avg_over_time(jvm_memory_used_bytes[1h])查看過去1h的使用情況,可以看到在00:10新生代飆升到500m左右完成后直接壓降:

老年帶控制在30m以內穩定攀升,還未到達gc臨界點,整體來看飆升的接口會很快被gc,所以系統整體情況良好:

為方便筆者直接通過jmap查看當前java進程情況,可以看到堆內存分配的2g左右的堆內存,此時的老年代也沒用到最大值僅僅動態擴容到83MB,整體內存使用情況良好:

Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 2065694720 (1970.0MB)
   NewSize                  = 42991616 (41.0MB)
   MaxNewSize               = 688390144 (656.5MB)
   OldSize                  = 87031808 (83.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

五、小結

本文深入分析了java常用計量儀micrometer中:

  • 有狀態累加計量器counter
  • 無狀態儀表盤gauge
  • 大量短耗時時間指標采集工具timer

基于這些指標我們結合通過promQL函數進行多維度的演示并給出了日常生產故障分析和排查步驟,希望對你有幫助。

責任編輯:趙寧寧 來源: 寫代碼的SharkChili
相關推薦

2021-07-01 11:29:45

KubernetesGrafana監控

2014-07-22 10:08:48

微軟監控開發者

2023-12-27 18:05:13

2020-11-20 08:15:40

Grafana + P

2021-12-25 22:31:55

Sentry 監控SDK 開發 性能監控

2023-02-28 22:52:47

2020-12-30 05:34:25

監控PrometheusGrafana

2023-08-24 07:46:21

服務器JVM

2023-11-01 09:44:21

MySQLJava

2023-04-26 00:01:04

2022-07-29 21:23:54

Grafana微服務

2017-05-28 10:03:23

服務器監控機架式

2015-07-06 10:31:50

Java開發者監控工具

2020-05-18 12:04:17

PrometheusMySQL監控

2023-10-09 07:31:25

2023-12-27 08:47:41

PrometheusLinux架構

2021-04-07 14:53:09

Prometheus開源監控

2025-11-11 07:20:00

SpringBoot架構監控埋點

2023-09-06 08:46:47

2023-10-11 09:58:07

點贊
收藏

51CTO技術棧公眾號

亚洲色成人一区二区三区小说| 成人91视频| 亚洲毛片亚洲毛片亚洲毛片| 一区二区三区日本视频| 一区二区成人在线| 欧美一区二区视频17c| 91av国产精品| 99这里有精品| www.日韩.com| 天天插天天射天天干| 国产韩日精品| 香蕉成人伊视频在线观看| 天天人人精品| 天堂在线视频免费| 精品无人码麻豆乱码1区2区 | free性欧美hd另类精品| 99精品视频在线播放观看| 成人国产精品久久久久久亚洲| 久久久久久久国产精品毛片| 国产一区不卡| 精品国产3级a| 手机在线观看日韩av| 久久人体大尺度| 亚洲一本大道在线| 国产对白在线播放| 成人亚洲性情网站www在线观看| 懂色av一区二区三区免费观看| 国产精品中文字幕在线| 国产又大又粗又爽| 亚洲日本国产| 欧美第一黄网免费网站| 亚洲女人久久久| 精品久久久久久久久久久下田| 精品国产乱码久久久久久久| 手机av在线免费| 在线国产成人影院| 午夜天堂影视香蕉久久| 黄色成人在线免费观看| chinese偷拍一区二区三区| 99久久精品国产毛片| 91丝袜美腿美女视频网站| 久久精品99北条麻妃| 久久久999| 欧美在线视频播放| 天天干天天干天天干天天| 亚洲三级网站| 国内自拍欧美激情| 国产第100页| 一区视频在线| 久久人人爽人人爽人人片av高请 | 国产精品久久久久久久久久久新郎 | 国产一区二区精品久| 日韩精品视频免费在线观看| 亚洲观看黄色网| 波多野结衣欧美| 精品成人私密视频| 亚洲国产精品自拍视频| 色狼人综合干| 亚洲欧美激情另类校园| 人妻av无码一区二区三区| 国产99久久精品一区二区300| 日韩精品在线视频| 91网站免费视频| 精品国产一级毛片| 色av吧综合网| 裸体武打性艳史| 欧美视频日韩| 7777kkkk成人观看| 亚洲毛片一区二区三区| 日产国产欧美视频一区精品| 国产精品热视频| 91麻豆国产视频| 国产电影一区在线| 精品婷婷色一区二区三区蜜桃| 日韩美女一级视频| 欧美韩国日本一区| 永久免费网站视频在线观看| 久久亚洲导航| 色综合咪咪久久| 一级黄色录像在线观看| 国产免费区一区二区三视频免费 | 三区在线观看| 国产日韩欧美激情| 毛片av在线播放| 久久男人天堂| 欧美午夜不卡在线观看免费| 乳色吐息在线观看| 亚洲精品国产动漫| 神马久久桃色视频| 国产又爽又黄的视频| 日本伊人色综合网| 不卡一卡2卡3卡4卡精品在| 亚洲日本中文字幕在线| 国产精品卡一卡二卡三| 99久久免费观看| 成人免费直播| 91精品中文字幕一区二区三区| 免费看黄色片的网站| 精品久久久久久久久久久aⅴ| 欧美超级免费视 在线| 九九精品免费视频| 国产成人综合亚洲网站| 日韩wuma| av资源网在线播放| 欧美精品一二三区| 成人h动漫精品一区| 欧美成人自拍| 国产不卡一区二区在线播放| www香蕉视频| 国产精品福利电影一区二区三区四区| 黄色国产一级视频| 精品国产鲁一鲁****| 国产小视频国产精品| 久久人人爽人人爽人人| 日本亚洲天堂网| 久久精品国产美女| 日韩三级电影视频| 777午夜精品视频在线播放| 亚洲精品女人久久久| 欧美日韩亚洲国产精品| 国产精品专区h在线观看| 日韩资源在线| 午夜天堂影视香蕉久久| 红桃视频一区二区三区免费| 日韩av自拍| 日韩免费视频在线观看| 少妇高潮久久久| 亚洲一区在线观看免费| 日韩av加勒比| 欧美jizz| 成人激情在线观看| 大片免费播放在线视频| 狠狠色噜噜狠狠狠狠97| 亚洲啪av永久无码精品放毛片 | 91在线码无精品| 国产精品又粗又长| 黑人久久a级毛片免费观看| 久久天堂电影网| 97超碰人人模人人人爽人人爱| 国产欧美日韩不卡免费| 成人性视频欧美一区二区三区| 精品丝袜久久| 91禁外国网站| 特级丰满少妇一级aaaa爱毛片| 亚洲综合免费观看高清完整版| 亚洲自拍第三页| 中文字幕一区二区三区在线视频| 成人乱色短篇合集| 成人影院www在线观看| 91精品国产综合久久香蕉麻豆| 91ts人妖另类精品系列| 久久国产视频网| 宅男噜噜99国产精品观看免费| 久久精品国产精品亚洲毛片| 深夜精品寂寞黄网站在线观看| 中文字幕一二区| ㊣最新国产の精品bt伙计久久| 欧美成人乱码一二三四区免费| 久久要要av| 91在线免费观看网站| 欧美24videosex性欧美| 亚洲精品国产欧美| 国产一级淫片a视频免费观看| 久久久久久综合| av在线网址导航| 一本精品一区二区三区| 成人自拍视频网站| 欲香欲色天天天综合和网| 亚洲乱码一区二区| 波多野结衣av无码| 亚洲欧洲日韩综合一区二区| 亚洲妇女无套内射精| 亚洲清纯自拍| 亚洲国产婷婷香蕉久久久久久99| 久久爱.com| 色综合久久88| 欧美另类自拍| 91精品国产欧美一区二区18| 久青草视频在线观看| 91小视频免费观看| 一区二区三区四区毛片| 1000部精品久久久久久久久| 日韩欧美国产二区| 麻豆国产一区二区三区四区| 91精品国产网站| 91大神xh98hx在线播放| 欧美www视频| 成人一二三四区| 亚洲综合色自拍一区| 国产sm调教视频| 国产成人av电影在线播放| 国产成人亚洲精品无码h在线| 日韩在线观看电影完整版高清免费悬疑悬疑| 成人乱色短篇合集| 97成人资源| 久久av.com| 电影在线高清| 日韩av有码在线| 国产精品久久久久久免费播放| 午夜在线成人av| 182在线观看视频| 久久综合成人精品亚洲另类欧美 | 久久一区二区视频| 先锋资源在线视频| 人人超碰91尤物精品国产| 免费拍拍拍网站| 91视频一区| 欧美污视频久久久| 久久亚州av| 97久久天天综合色天天综合色hd| 韩日一区二区| 97超级碰碰人国产在线观看| 黄a在线观看| 国产一区二区三区四区福利| 熟妇人妻一区二区三区四区 | 色哟哟免费视频| 青青青伊人色综合久久| 日韩人妻精品无码一区二区三区| 综合av在线| 一区二区不卡在线观看| 最新亚洲精品| 久久婷婷人人澡人人喊人人爽| 日韩中文字幕一区二区高清99| 国产精品久久久久国产a级| 亚洲同志男男gay1069网站| 国产69精品久久久久99| www视频在线免费观看| 中文字幕成人在线| 二区三区在线播放| 亚洲天堂免费在线| 日韩一二三四| 亚洲免费视频网站| 色久视频在线播放| 日韩精品中文字幕久久臀| 刘玥91精选国产在线观看| 欧美一区二区三区系列电影| 国产一区二区三区在线观看| 欧美色视频在线| 中文天堂在线播放| 欧美午夜精品理论片a级按摩| 中文字幕在线观看视频免费| 欧美性高跟鞋xxxxhd| √资源天堂中文在线| 精品美女久久久久久免费| 亚洲国产精品成人无久久精品| 亚洲综合在线免费观看| 婷婷在线精品视频| 一区二区三区欧美久久| 欧美精品xxxxx| 亚洲一区二区精品视频| 久久精品免费av| 天天色综合天天| 久久99精品波多结衣一区| 欧美午夜精品久久久久久人妖| 久草视频在线观| 91传媒视频在线播放| 99re热视频| 欧美酷刑日本凌虐凌虐| 97超碰人人草| 欧美videossexotv100| 日本黄色三级视频| 国产午夜精品理论片a级探花| 精品无人乱码| 中文字幕亚洲无线码a| 久草中文在线观看| 欧美精品videofree1080p| 女海盗2成人h版中文字幕| 日本最新高清不卡中文字幕| av免费在线一区| 91在线免费视频| 久久97精品| 天堂av一区二区| 亚洲成人一区| 波多野结衣家庭教师在线播放| 久久高清免费观看| 天天摸天天舔天天操| 成人晚上爱看视频| 蜜桃传媒一区二区亚洲| 亚洲欧洲精品成人久久奇米网| 久久影院一区二区| 欧美亚洲国产怡红院影院| 国产黄色av网站| 精品亚洲aⅴ在线观看| 欧美激情免费| 午夜精品久久久久久久男人的天堂| 9i看片成人免费高清| 成人激情视频在线| 色婷婷精品视频| 中文字幕第一页亚洲| 亚洲少妇在线| 国产又黄又猛的视频| 久久综合资源网| 日本a级片视频| 色综合欧美在线| 亚洲欧美激情在线观看| 中文日韩在线视频| 2020国产在线| 国产主播在线一区| 亚洲三级性片| 日韩一级特黄毛片| 日韩1区2区3区| 中文字幕免费在线播放| 亚洲天堂免费看| 麻豆成人免费视频| 亚洲成人精品视频| 日本综合在线| 奇米成人av国产一区二区三区| 欧美激情三级| 亚洲一区二区三区精品视频 | 97色在线观看| 国产一区二区| 亚洲成人午夜在线| 性色av一区二区怡红| 黑人巨大猛交丰满少妇| 国产精品看片你懂得| 天天射天天干天天| 日韩精品视频观看| h片在线观看| 91在线直播亚洲| 国产精品国内免费一区二区三区| 黑鬼大战白妞高潮喷白浆| 成人av在线播放网站| 99热精品免费| 在线观看91av| 欧美激情午夜| 成人免费高清完整版在线观看| 精品国产91久久久久久浪潮蜜月| 国产综合中文字幕| 大桥未久av一区二区三区中文| 日韩精品一区二区亚洲av性色| 91久久精品一区二区三区| 日韩欧美电影在线观看| 欧美有码在线观看视频| 亚洲aaa级| 各处沟厕大尺度偷拍女厕嘘嘘| 成人av在线网| 99精品视频99| 亚洲激情视频在线| 欧美freesex黑人又粗又大| 狠狠综合久久av| 在线一区免费观看| 成人无码www在线看免费| 亚洲国产精品久久艾草纯爱| 性中国xxx极品hd| 欧美精品久久久久久久久| 成人涩涩网站| heyzo亚洲| 久久综合色之久久综合| 精人妻无码一区二区三区| 亚洲色图35p| 91精品国产66| 一区一区视频| 国产精品一区二区久激情瑜伽 | 在线日本视频| 国产中文字幕亚洲| 欧美国产三级| 中文在线观看免费视频| 第一福利永久视频精品| 极品美乳网红视频免费在线观看| 国产精品成人一区二区三区吃奶| 精品毛片免费观看| 天堂中文av在线| 一片黄亚洲嫩模| 视频二区在线| 国产女人18毛片水18精品| 国产精品二区不卡| 无码人妻一区二区三区精品视频| 亚洲国产你懂的| 欧美精品a∨在线观看不卡 | 久久精品一区二区三区不卡牛牛| 午夜婷婷在线观看| 日韩中文字幕在线视频| 中文字幕一区二区三区四区久久| 阿v天堂2017| 国产日韩精品一区二区三区在线| 国产伦子伦对白视频| 国模gogo一区二区大胆私拍| 免费观看不卡av| 激情在线观看视频| 欧美日韩一区免费| 免费黄色网页在线观看| 国产精品一区二区三区精品| 久久久蜜桃一区二区人| 成人免费毛片xxx| 亚洲男人天堂手机在线| 成人97精品毛片免费看| 3d动漫一区二区三区| 国产精品久久久久久久久晋中| 性一交一乱一伧老太| 国产精品黄页免费高清在线观看| 91综合在线| 人妻熟女aⅴ一区二区三区汇编| 欧美日韩午夜在线视频| 多野结衣av一区| 在线无限看免费粉色视频| 99久久久久久| 99久久亚洲精品日本无码 | 久久精品嫩草影院| jizzjizz国产精品喷水| 中文字幕视频一区|