在SpringBoot2.x中使用Prometheus

在上一篇文章(Prometheus在k8s上的安裝與使用)中墨吓,我們?cè)趉8s集群成功搭建了Prometheus服務(wù)。今天纹磺,我們將在springboot2.x中使用prometheus記錄指標(biāo)帖烘。

一、我們需要什么指標(biāo)

對(duì)于DDD橄杨、TDD等秘症,大家比較熟悉了,但是對(duì)于MDD可能就比較陌生了式矫。MDD是Metrics-Driven Development的縮寫(xiě)乡摹,主張開(kāi)發(fā)過(guò)程由指標(biāo)驅(qū)動(dòng),通過(guò)實(shí)用指標(biāo)來(lái)驅(qū)動(dòng)快速采转、精確和細(xì)粒度的軟件迭代聪廉。MDD可使所有可以測(cè)量的東西都得到量化和優(yōu)化,進(jìn)而為整個(gè)開(kāi)發(fā)過(guò)程帶來(lái)可見(jiàn)性故慈,幫助相關(guān)人員快速板熊、準(zhǔn)確地作出決策,并在發(fā)生錯(cuò)誤時(shí)立即發(fā)現(xiàn)問(wèn)題并修復(fù)察绷。依照MDD的理念干签,在需求階段就應(yīng)該考慮關(guān)鍵指標(biāo),在應(yīng)用上線后通過(guò)指標(biāo)了解現(xiàn)狀并持續(xù)優(yōu)化克婶。

有一些基于指標(biāo)的方法論筒严,建議大家了解一下:

  • Google的四大黃金指標(biāo):延遲Latency、流量Traffic情萤、錯(cuò)誤Errors鸭蛙、飽和度Saturation
  • Netflix的USE方法:使用率Utilization、飽和度Saturation筋岛、錯(cuò)誤Error
  • WeaveCloud的RED方法:速率Rate娶视、錯(cuò)誤Errors、耗時(shí)Duration

二、在SrpingBoot中引入prometheus

SpringBoot2.x集成Prometheus非常簡(jiǎn)單肪获,首先引入maven依賴(lài):

<dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-registry-prometheus</artifactId>
            <version>1.7.3</version>
        </dependency>
        <dependency>
            <groupId>io.github.mweirauch</groupId>
            <artifactId>micrometer-jvm-extras</artifactId>
            <version>0.2.2</version>
        </dependency>

然后寝凌,在application.properties中將prometheus的endpoint放出來(lái)。

management:
  endpoints:
    web:
      exposure:
        include: info,health,prometheus

接下來(lái)就可以進(jìn)行指標(biāo)埋點(diǎn)了孝赫,Prometheus的四種指標(biāo)類(lèi)型此處不再贅述较木,請(qǐng)自行學(xué)習(xí)。一般指標(biāo)埋點(diǎn)代碼實(shí)現(xiàn)上有兩種形式:AOP青柄、侵入式伐债,建議盡量使用AOP記錄指標(biāo),對(duì)于無(wú)法使用aop的場(chǎng)景就只能侵入代碼了致开。常用的AOP方式有:

  • @Aspect(通用)
  • HandlerInterceptor (SpringMVC的攔截器)
  • ClientHttpRequestInterceptor (RestTemplate的攔截器)
  • DubboFilter (dubbo接口)

我們選擇通用的@Aspect峰锁,結(jié)合自定義指標(biāo)注解來(lái)實(shí)現(xiàn)。首先自定義指標(biāo)注解:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodMetrics {
    String name() default "";
    String desc() default "";
    String[] tags() default {};
    //是否記錄時(shí)間間隔
    boolean withoutDuration() default false;
}

然后是切面實(shí)現(xiàn):

@Aspect
public class PrometheusAnnotationAspect {

    @Autowired
    private MeterRegistry meterRegistry;

    @Pointcut("@annotation(com.smac.prometheus.annotation.MethodMetrics)")
    public void pointcut() {}

    @Around(value = "pointcut()")
    public Object process(ProceedingJoinPoint joinPoint) throws Throwable {
        Method targetMethod = ((MethodSignature) joinPoint.getSignature()).getMethod();
        Method currentMethod = ClassUtils.getUserClass(joinPoint.getTarget().getClass()).getDeclaredMethod(targetMethod.getName(), targetMethod.getParameterTypes());
        if (currentMethod.isAnnotationPresent(MethodMetrics.class)) {
            MethodMetrics methodMetrics = currentMethod.getAnnotation(MethodMetrics.class);
            return processMetric(joinPoint, currentMethod, methodMetrics);
        } else {
            return joinPoint.proceed();
        }
    }

    private Object processMetric(ProceedingJoinPoint joinPoint, Method currentMethod, MethodMetrics methodMetrics) {
        String name = methodMetrics.name();
        if (!StringUtils.hasText(name)) {
            name = currentMethod.getName();
        }
        String desc = methodMetrics.desc();
        if (!StringUtils.hasText(desc)) {
            desc = currentMethod.getName();
        }
        //不需要記錄時(shí)間
        if (methodMetrics.withoutDuration()) {
            Counter counter = Counter.builder(name).tags(methodMetrics.tags()).description(desc).register(meterRegistry);
            try {
                return joinPoint.proceed();
            } catch (Throwable e) {
                throw new IllegalStateException(e);
            } finally {
                counter.increment();
            }
        }
        //需要記錄時(shí)間(默認(rèn))
        Timer timer = Timer.builder(name).tags(methodMetrics.tags()).description(desc).register(meterRegistry);
        return timer.record(() -> {
            try {
                return joinPoint.proceed();
            } catch (Throwable e) {
                throw new IllegalStateException(e);
            }
        });
    }
}

代碼很容易双戳,沒(méi)什么可說(shuō)明的虹蒋,接下來(lái)就是在需要記監(jiān)控的地方加上這個(gè)注解就行,比如:

@MethodMetrics(name="sms_send",tags = {"vendor","aliyun"})
public void send(String mobile, SendMessage message) throws Exception {
    ...
}

至此飒货,aop形式的指標(biāo)實(shí)現(xiàn)方式就完成了魄衅。如果是侵入式的話,直接使用meterRegistry就行:

meterRegistry.counter("sms.send","vendor","aliyun").increment();

啟動(dòng)服務(wù)膏斤,打開(kāi)http://localhost:8080/actuator/prometheus查看指標(biāo)徐绑。

三、高級(jí)指標(biāo)之分位數(shù)

分位數(shù)(P50/P90/P95/P99)是我們常用的一個(gè)性能指標(biāo)莫辨,Prometheus提供了兩種解決方案:

client側(cè)計(jì)算方案

summery類(lèi)型,設(shè)置percentiles毅访,在本地計(jì)算出Pxx沮榜,作為指標(biāo)的一個(gè)tag被直接收集。

Timer timer = Timer.builder("sms.send").publishPercentiles(0.5, 0.9, 0.95,0.99).register(meterRegistry);
timer.record(costTime, TimeUnit.MILLISECONDS);

會(huì)出現(xiàn)四個(gè)帶quantile的指標(biāo)喻粹,如圖:
quantile
server側(cè)計(jì)算方案

開(kāi)啟histogram蟆融,將所有樣本放入buckets中,在server側(cè)通過(guò)histogram_quantile函數(shù)對(duì)buckets進(jìn)行實(shí)時(shí)計(jì)算得出守呜。注意:histogram采用了線性插值法型酥,buckets的劃分對(duì)誤差的影響比較大,需合理設(shè)置查乒。

Timer timer = Timer.builder("sms.send")
                .publishPercentileHistogram(true)
                .serviceLevelObjectives(Duration.ofMillis(10),Duration.ofMillis(20),Duration.ofMillis(50))
                .minimumExpectedValue(Duration.ofMillis(1))
                .maximumExpectedValue(Duration.ofMillis(100))
                .register(meterRegistry);
timer.record(costTime, TimeUnit.MILLISECONDS);

會(huì)出現(xiàn)一堆xxxx_bucket的指標(biāo)弥喉,如圖:

buckets

然后,使用histogram_quantile(0.95, rate(sms_send_seconds_bucket[5m]))就可以看到P95的指標(biāo)了玛迄,如圖:
histogram_quantile

結(jié)論:方案1適用于單機(jī)或只關(guān)心本地運(yùn)行情況的指標(biāo)欧引,比如gc時(shí)間咒钟、定時(shí)任務(wù)執(zhí)行時(shí)間秘蛇、本地緩存更新時(shí)間等跺株;方案2則適用于分布式環(huán)境下的整體運(yùn)行情況的指標(biāo),比如搜索接口的響應(yīng)時(shí)間马胧、第三方接口的響應(yīng)時(shí)間等。

相關(guān)閱讀
  1. prometheus兩種分位值histogram和summary對(duì)比histogram線性插值法原理
  2. 使用Prometheus計(jì)算百分位數(shù)值
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市瘸彤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌笛钝,老刑警劉巖质况,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異婆翔,居然都是意外死亡拯杠,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)啃奴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)潭陪,“玉大人,你說(shuō)我怎么就攤上這事最蕾∫浪荩” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵瘟则,是天一觀的道長(zhǎng)黎炉。 經(jīng)常有香客問(wèn)我,道長(zhǎng)醋拧,這世上最難降的妖魔是什么慷嗜? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮丹壕,結(jié)果婚禮上庆械,老公的妹妹穿的比我還像新娘。我一直安慰自己菌赖,他們只是感情好缭乘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著琉用,像睡著了一般堕绩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上邑时,一...
    開(kāi)封第一講書(shū)人閱讀 51,482評(píng)論 1 302
  • 那天奴紧,我揣著相機(jī)與錄音,去河邊找鬼刁愿。 笑死绰寞,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播滤钱,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼觉壶,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了件缸?” 一聲冷哼從身側(cè)響起铜靶,我...
    開(kāi)封第一講書(shū)人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎他炊,沒(méi)想到半個(gè)月后争剿,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡痊末,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年蚕苇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凿叠。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涩笤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出盒件,到底是詐尸還是另有隱情蹬碧,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布炒刁,位于F島的核電站恩沽,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏翔始。R本人自食惡果不足惜罗心,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望城瞎。 院中可真熱鬧协屡,春花似錦、人聲如沸全谤。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)认然。三九已至,卻和暖如春漫萄,著一層夾襖步出監(jiān)牢的瞬間卷员,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工腾务, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留毕骡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像未巫,于是被迫代替她去往敵國(guó)和親窿撬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容