基于Prometheus搭建SpringCloud全方位立體監(jiān)控體系

前提

最近公司在聯(lián)合運(yùn)維做一套全方位監(jiān)控的系統(tǒng)享郊,應(yīng)用集群的技術(shù)棧是SpringCloud體系壮啊。雖然本人沒有參與具體基礎(chǔ)架構(gòu)的研發(fā),但是從應(yīng)用引入的包和一些資料的查閱大致推算出具體的實(shí)現(xiàn)方案,這里做一次推演灭衷,詳細(xì)記錄一下整個(gè)搭建過程。

Prometheus是什么

Prometheus(普羅米修斯谬墙,官網(wǎng)是https://prometheus.io/)今布,是一個(gè)開源的系統(tǒng)監(jiān)控和告警的工具包,其采用Pull方式采集時(shí)間序列的度量數(shù)據(jù)拭抬,通過Http協(xié)議傳輸部默。它的工作方式是被監(jiān)控的服務(wù)需要公開一個(gè)Prometheus端點(diǎn),這端點(diǎn)是一個(gè)HTTP接口造虎,該接口公開了度量的列表和當(dāng)前的值傅蹂,然后Prometheus應(yīng)用從此接口定時(shí)拉取數(shù)據(jù),一般可以存放在時(shí)序數(shù)據(jù)庫中算凿,然后通過可視化的Dashboard(例如Promdash或者Grafana)進(jìn)行數(shù)據(jù)展示份蝴。當(dāng)然,此文章不打算深入研究這個(gè)工具氓轰,只做應(yīng)用層面的展示婚夫。這篇文章將會用到下面幾個(gè)技術(shù)棧:

  • SpringCloud體系,主要是注冊中心和注冊客戶端署鸡。
  • spring-boot-starter-actuator案糙,主要是提供了Prometheus端點(diǎn)限嫌,不用重復(fù)造輪子。
  • Prometheus的Java客戶端时捌。
  • Prometheus應(yīng)用怒医。
  • io.micrometer,SpringBoot標(biāo)準(zhǔn)下使用的度量工具包奢讨。
  • Grafana稚叹,可視化的Dashboard。

這里所有的軟件或者依賴全部使用當(dāng)前的最新版本拿诸,如果有坑踩了再填扒袖。其實(shí),Prometheus本身也開發(fā)了一套Counter佳镜、Gauge僚稿、Timer等相關(guān)接口,不過SpringBoot中使用了io.micrometer中的套件蟀伸,所以本文不深入分析Prometheus的Java客戶端蚀同。

io.micrometer的使用

在SpringBoot2.X中,spring-boot-starter-actuator引入了io.micrometer啊掏,對1.X中的metrics進(jìn)行了重構(gòu)蠢络,主要特點(diǎn)是支持tag/label,配合支持tag/label的監(jiān)控系統(tǒng)迟蜜,使得我們可以更加方便地對metrics進(jìn)行多維度的統(tǒng)計(jì)查詢及監(jiān)控刹孔。io.micrometer目前支持Counter、Gauge娜睛、Timer髓霞、Summary等多種不同類型的度量方式(不知道有沒有遺漏),下面逐個(gè)簡單分析一下它們的作用和使用方式畦戒。 需要在SpringBoot項(xiàng)目下引入下面的依賴:

<dependency>
  <groupId>io.micrometer</groupId>
  <artifactId>micrometer-core</artifactId>
  <version>${micrometer.version}</version>
</dependency>

目前最新的micrometer.version為1.0.5方库。注意一點(diǎn)的是:io.micrometer支持Tag(標(biāo)簽)的概念,Tag是其metrics是否能夠有多維度的支持的基礎(chǔ)障斋,Tag必須成對出現(xiàn)纵潦,也就是必須配置也就是偶數(shù)個(gè)Tag,有點(diǎn)類似于K-V的關(guān)系垃环。

Counter

Counter(計(jì)數(shù)器)簡單理解就是一種只增不減的計(jì)數(shù)器邀层。它通常用于記錄服務(wù)的請求數(shù)量、完成的任務(wù)數(shù)量遂庄、錯誤的發(fā)生數(shù)量等等寥院。舉個(gè)例子:

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;

/**
 * @author throwable
 * @version v1.0
 * @description
 * @since 2018/7/19 23:10
 */
public class CounterSample {

    public static void main(String[] args) throws Exception {
        //tag必須成對出現(xiàn),也就是偶數(shù)個(gè)
        Counter counter = Counter.builder("counter")
                .tag("counter", "counter")
                .description("counter")
                .register(new SimpleMeterRegistry());
        counter.increment();
        counter.increment(2D);
        System.out.println(counter.count());
        System.out.println(counter.measure());
        //全局靜態(tài)方法
        Metrics.addRegistry(new SimpleMeterRegistry());
        counter = Metrics.counter("counter", "counter", "counter");
        counter.increment(10086D);
        counter.increment(10087D);
        System.out.println(counter.count());
        System.out.println(counter.measure());
    }
}

輸出:

3.0
[Measurement{statistic='COUNT', value=3.0}]
20173.0
[Measurement{statistic='COUNT', value=20173.0}]

Counter的Measurement的statistic(可以理解為度量的統(tǒng)計(jì)角度)只有COUNT涛目,也就是它只具備計(jì)數(shù)(它只有增量的方法只磷,因此只增不減)经磅,這一點(diǎn)從它的接口定義可知:

public interface Counter extends Meter {

  default void increment() {
        increment(1.0);
  }

  void increment(double amount);

  double count();

  //忽略其他方法或者成員
}

Counter還有一個(gè)衍生類型FunctionCounter,它是基于函數(shù)式接口ToDoubleFunction進(jìn)行計(jì)數(shù)統(tǒng)計(jì)的钮追,用法差不多。

Gauge

Gauge(儀表)是一個(gè)表示單個(gè)數(shù)值的度量阿迈,它可以表示任意地上下移動的數(shù)值測量元媚。Gauge通常用于變動的測量值,如當(dāng)前的內(nèi)存使用情況苗沧,同時(shí)也可以測量上下移動的"計(jì)數(shù)"刊棕,比如隊(duì)列中的消息數(shù)量。舉個(gè)例子:

import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author throwable
 * @version v1.0
 * @description
 * @since 2018/7/19 23:30
 */
public class GaugeSample {

    public static void main(String[] args) throws Exception {
        AtomicInteger atomicInteger = new AtomicInteger();
        Gauge gauge = Gauge.builder("gauge", atomicInteger, AtomicInteger::get)
                .tag("gauge", "gauge")
                .description("gauge")
                .register(new SimpleMeterRegistry());
        atomicInteger.addAndGet(5);
        System.out.println(gauge.value());
        System.out.println(gauge.measure());
        atomicInteger.decrementAndGet();
        System.out.println(gauge.value());
        System.out.println(gauge.measure());
        //全局靜態(tài)方法待逞,返回值竟然是依賴值甥角,有點(diǎn)奇怪,暫時(shí)不選用
        Metrics.addRegistry(new SimpleMeterRegistry());
        AtomicInteger other = Metrics.gauge("gauge", atomicInteger, AtomicInteger::get);
    }
}

輸出結(jié)果:

5.0
[Measurement{statistic='VALUE', value=5.0}]
4.0
[Measurement{statistic='VALUE', value=4.0}]

Gauge關(guān)注的度量統(tǒng)計(jì)角度是VALUE(值)识樱,它的構(gòu)建方法中依賴于函數(shù)式接口ToDoubleFunction的實(shí)例(如例子中的實(shí)例方法引用AtomicInteger::get)和一個(gè)依賴于ToDoubleFunction改變自身值的實(shí)例(如例子中的AtomicInteger實(shí)例)嗤无,它的接口方法如下:

public interface Gauge extends Meter {

  double value();

  //忽略其他方法或者成員
}

Timer

Timer(計(jì)時(shí)器)同時(shí)測量一個(gè)特定的代碼邏輯塊的調(diào)用(執(zhí)行)速度和它的時(shí)間分布。簡單來說怜庸,就是在調(diào)用結(jié)束的時(shí)間點(diǎn)記錄整個(gè)調(diào)用塊執(zhí)行的總時(shí)間当犯,適用于測量短時(shí)間執(zhí)行的事件的耗時(shí)分布,例如消息隊(duì)列消息的消費(fèi)速率割疾。舉個(gè)例子:

import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;

import java.util.concurrent.TimeUnit;

/**
 * @author throwable
 * @version v1.0
 * @description
 * @since 2018/7/19 23:44
 */
public class TimerSample {

    public static void main(String[] args) throws Exception{
        Timer timer = Timer.builder("timer")
                .tag("timer","timer")
                .description("timer")
                .register(new SimpleMeterRegistry());
        timer.record(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
            }catch (InterruptedException e){
                //ignore
            }
        });
        System.out.println(timer.count());
        System.out.println(timer.measure());
        System.out.println(timer.totalTime(TimeUnit.SECONDS));
        System.out.println(timer.mean(TimeUnit.SECONDS));
        System.out.println(timer.max(TimeUnit.SECONDS));
    }
}

輸出結(jié)果:

1
[Measurement{statistic='COUNT', value=1.0}, Measurement{statistic='TOTAL_TIME', value=2.000603975}, Measurement{statistic='MAX', value=2.000603975}]
2.000603975
2.000603975
2.000603975

Timer的度量統(tǒng)計(jì)角度主要包括記錄執(zhí)行的最大時(shí)間嚎卫、總時(shí)間、平均時(shí)間宏榕、執(zhí)行完成的總?cè)蝿?wù)數(shù)拓诸,它提供多種的統(tǒng)計(jì)方法變體:

public interface Timer extends Meter, HistogramSupport {

  void record(long amount, TimeUnit unit);

  default void record(Duration duration) {
      record(duration.toNanos(), TimeUnit.NANOSECONDS);
  }

  <T> T record(Supplier<T> f);
    
  <T> T recordCallable(Callable<T> f) throws Exception;

  void record(Runnable f);

  default Runnable wrap(Runnable f) {
      return () -> record(f);
  }

  default <T> Callable<T> wrap(Callable<T> f) {
    return () -> recordCallable(f);
  }

  //忽略其他方法或者成員
}

這些record或者包裝方法可以根據(jù)需要選擇合適的使用,另外麻昼,一些度量屬性(如下限和上限)或者單位可以自行配置奠支,具體屬性的相關(guān)內(nèi)容可以查看DistributionStatisticConfig類,這里不詳細(xì)展開涌献。

另外胚宦,Timer有一個(gè)衍生類LongTaskTimer,主要是用來記錄正在執(zhí)行但是尚未完成的任務(wù)數(shù)燕垃,用法差不多枢劝。

Summary

Summary(摘要)用于跟蹤事件的分布。它類似于一個(gè)計(jì)時(shí)器卜壕,但更一般的情況是您旁,它的大小并不一定是一段時(shí)間的測量值。在micrometer中轴捎,對應(yīng)的類是DistributionSummary鹤盒,它的用法有點(diǎn)像Timer蚕脏,但是記錄的值是需要直接指定,而不是通過測量一個(gè)任務(wù)的執(zhí)行時(shí)間侦锯。舉個(gè)例子:


import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;

/**
 * @author throwable
 * @version v1.0
 * @description
 * @since 2018/7/19 23:55
 */
public class SummarySample {

    public static void main(String[] args) throws Exception {
        DistributionSummary summary = DistributionSummary.builder("summary")
                .tag("summary", "summary")
                .description("summary")
                .register(new SimpleMeterRegistry());
        summary.record(2D);
        summary.record(3D);
        summary.record(4D);
        System.out.println(summary.measure());
        System.out.println(summary.count());
        System.out.println(summary.max());
        System.out.println(summary.mean());
        System.out.println(summary.totalAmount());
    }
}

輸出結(jié)果:

[Measurement{statistic='COUNT', value=3.0}, Measurement{statistic='TOTAL', value=9.0}, Measurement{statistic='MAX', value=4.0}]
3
4.0
3.0
9.0

Summary的度量統(tǒng)計(jì)角度主要包括記錄過的數(shù)據(jù)中的最大值驼鞭、總數(shù)值、平均值和總次數(shù)尺碰。另外挣棕,一些度量屬性(如下限和上限)或者單位可以自行配置,具體屬性的相關(guān)內(nèi)容可以查看DistributionStatisticConfig類亲桥,這里不詳細(xì)展開洛心。

小結(jié)

一般情況下,上面的Counter题篷、Gauge词身、Timer、DistributionSummary例子可以滿足日常開發(fā)需要番枚,但是有些高級的特性這里沒有展開法严,具體可以參考micrometer-spring-legacy這個(gè)依賴包,畢竟源碼是老師户辫,源碼不會騙人渐夸。

spring-boot-starter-actuator的使用

spring-boot-starter-actuator在2.X版本中不僅升級了metrics為io.micrometer,很多配置方式也和1.X完全不同渔欢,鑒于前段時(shí)間沒有維護(hù)SpringBoot技術(shù)棧的項(xiàng)目墓塌,現(xiàn)在重新看了下官網(wǎng)復(fù)習(xí)一下。引入依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    <version>${springboot.version}</version>
</dependency>

目前最新的springboot.version為2.0.3.RELEASE奥额。在spring-boot-starter-actuator中苫幢,最大的變化就是配置的變化,原來在1.X版本是通過management.security.enabled控制是否可以忽略權(quán)限訪問所有的監(jiān)控端點(diǎn)垫挨,在2.X版本中韩肝,必須顯式配置不需要權(quán)限驗(yàn)證對外開放的端點(diǎn):

management.endpoints.web.exposure.include=*
management.endpoints.web.exposure.exclude=env,beans
management.endpoints.jmx.exposure.include=
management.endpoints.jmx.exposure.include=*

例如上面的配置,訪問非/env和非/beans的端點(diǎn)九榔,可以不受權(quán)限控制哀峻,也就是所有人都可以訪問非/env和非/beans的端點(diǎn)。例如哲泊,如果我只想暴露/health端點(diǎn)剩蟀,只需配置:

management.endpoints.web.exposure.include=health

這一點(diǎn)需要特別注意,其他使用和1.X差不多切威。還有一點(diǎn)是育特,2.X中所有監(jiān)控端點(diǎn)的訪問url的默認(rèn)路徑前綴為:http://{host}/{port}/actuator/,也就是想訪問health端點(diǎn)就要訪問http://{host}/{port}/actuator/health先朦,當(dāng)然也可以修改/actuator這個(gè)路徑前綴缰冤。其他細(xì)節(jié)區(qū)別沒有深入研究犬缨,可以參考文檔

搭建SpringCloud應(yīng)用

接著先搭建一個(gè)SpringCloud應(yīng)用群棉浸,主要包括注冊中心(registry-center)和一個(gè)簡單的服務(wù)節(jié)點(diǎn)(cloud-prometheus-sample)怀薛,其中注冊中心只引入eureka-server的依賴,而服務(wù)節(jié)點(diǎn)用于對接Prometheus涮拗,引入eureka-client乾戏、spring-boot-starter-actuator、prometheus等依賴三热。

registry-center

registry-center是一個(gè)單純的服務(wù)注冊中心,只需要引入eureka-server的依賴三幻,添加一個(gè)啟動類即可就漾,添加的依賴如下:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

添加一個(gè)啟動類:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

/**
 * @author throwable
 * @version v1.0
 * @description
 * @since 2018/7/21 9:06
 */
@SpringBootApplication
@EnableEurekaServer
public class RegistryCenterApplication {

    public static void main(String[] args) {
        SpringApplication.run(RegistryCenterApplication.class, args);
    }
}

配置文件application.yaml如下:

server:
  port: 9091
spring:
  application:
    name: registry-center
eureka:
  instance:
    hostname: localhost
  client:
    enabled: true
    register-with-eureka: false
    fetch-registry: false
    service-url:
         defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

就是這么簡單,啟動入口類即可念搬,啟動的端口為9091抑堡。

cloud-prometheus-sample

cloud-prometheus-sample主要作為eureka-client,接入spring-boot-starter-actuator和prometheus依賴朗徊,引入依賴如下:

<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

這里引入的是micrometer-registry-prometheus而不是micrometer-spring-legacy是因?yàn)?code>micrometer-spring-legacy是spring-integration(spring系統(tǒng)集成)的依賴首妖,這里沒有用到,但是里面很多實(shí)現(xiàn)可以參考爷恳。micrometer-registry-prometheus提供了基于actuator的端點(diǎn)有缆,路徑是../prometheus。啟動類如下:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * @author throwable
 * @version v1.0
 * @description
 * @since 2018/7/21 9:13
 */
@SpringBootApplication
@EnableEurekaClient
public class SampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(SampleApplication.class, args);
    }
}

配置文件application.yaml如下:

server:
  port: 9092
spring:
  application:
    name: cloud-prometheus-sample
eureka:
  instance:
    hostname: localhost
  client:
    service-url: http://localhost:9091/eureka/

啟動端口為9092温亲,eureka的服務(wù)注冊地址為:http://localhost:9091/eureka/棚壁,也就是registry-center中指定的默認(rèn)數(shù)據(jù)區(qū)(defaultZone)的注冊地址,先啟動registry-center栈虚,再啟動cloud-prometheus-sample袖外,然后訪問http://localhost:9091/

sp-p-1

訪問http://localhost:9092/actuator/prometheus

sp-p-2

這些數(shù)據(jù)就是實(shí)時(shí)的度量數(shù)據(jù),Prometheus(軟件)配置好任務(wù)并且啟動執(zhí)行后魂务,就是通過定時(shí)拉取/prometheus這個(gè)端點(diǎn)返回的數(shù)據(jù)進(jìn)行數(shù)據(jù)聚合和展示的曼验。

接著,我們先定制一個(gè)功能粘姜,統(tǒng)計(jì)cloud-prometheus-sample所有入站的Http請求數(shù)量(包括成功鬓照、失敗和非法的),添加如下代碼:

//請求攔截器
@Component
public class SampleMvcInterceptor extends HandlerInterceptorAdapter {

    private static final Counter COUNTER = Counter.builder("Http請求統(tǒng)計(jì)")
            .tag("HttpCount", "HttpCount")
            .description("Http請求統(tǒng)計(jì)")
            .register(Metrics.globalRegistry);

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
        COUNTER.increment();
    }
}
//自定義Mvc配置
@Component
public class SampleWebMvcConfigurer implements WebMvcConfigurer {

    @Autowired
    private SampleMvcInterceptor sampleMvcInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(sampleMvcInterceptor);
    }
}

重啟cloud-prometheus-sample相艇,直接訪問幾次不存在的根節(jié)點(diǎn)路徑http://localhost:9092/颖杏,再查看端點(diǎn)統(tǒng)計(jì)數(shù)據(jù):

sp-p-3

安裝和使用Prometheus

先從Prometheus官方下載地址下載軟件,這里用Windows10平臺演示坛芽,直接下載prometheus-2.3.2.windows-amd64.tar.gz留储,個(gè)人有軟件潔癖翼抠,用軟件或者依賴喜歡最高版本,出現(xiàn)坑了自己填获讳。解壓后目錄如下:

sp-p-4

啟動的話阴颖,直接運(yùn)行prometheus.exe即可,這里先配置一下prometheus.yml:

global:
  scrape_interval:     15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
alerting:
  alertmanagers:
  - static_configs:
    - targets:
      # - alertmanager:9093
scrape_configs:
  - job_name: 'prometheus'
    metrics_path: /actuator/prometheus
    static_configs:
    - targets: ['localhost:9092']

我們主要修改的是scrape_configs節(jié)點(diǎn)下的配置丐膝,這個(gè)節(jié)點(diǎn)時(shí)配置同步任務(wù)的量愧,這里配置一個(gè)任務(wù)為'prometheus',拉取數(shù)據(jù)的路徑為/actuator/prometheus帅矗,目標(biāo)host-port為'localhost:9092'偎肃,也就是cloud-prometheus-sample暴露的prometheus端點(diǎn),Prometheus(軟件)的默認(rèn)啟動端口為9090浑此。啟動后累颂,同級目錄下會生成一個(gè)data目錄,實(shí)際上起到"時(shí)序數(shù)據(jù)庫"的類似作用凛俱。訪問Prometheus(軟件)的控制臺http://localhost:9090/targets:

sp-p-5
sp-p-6

Prometheus度量統(tǒng)計(jì)的所有監(jiān)控項(xiàng)可以在http://localhost:9090/graph中查看到紊馏。這里可以觀察到HttpCount的統(tǒng)計(jì),但是界面不夠炫酷蒲犬,配置項(xiàng)也少朱监,因此需要引入Grafana。

安裝和接入Grafana

Grafana的安裝也十分簡單原叮,它也是開箱即用的赫编,就是配置的時(shí)候需要熟悉它的語法。先到Grafana官網(wǎng)下載頁面下載一個(gè)適合系統(tǒng)的版本篇裁,這里選擇Windows版本沛慢。解壓之后,直接運(yùn)行bin目錄下的grafana-server.exe即可达布,默認(rèn)的啟動端口是3000团甲,訪問http://localhost:3000/,初始化賬號密碼是admin/admin黍聂,首次登陸需要修改密碼躺苦,接著添加一個(gè)數(shù)據(jù)源:

sp-p-7

接著添加一個(gè)新的命名為'sample'的Dashboard,添加一個(gè)Graph類型的Panel产还,配置其屬性:

sp-p-8
sp-p-9

A記錄(查詢命令)就是對應(yīng)http://localhost:9090/graph中的查詢命令的目標(biāo):

sp-p-10

很簡單历谍,配置完畢之后伊者,就可以看到高大上的統(tǒng)計(jì)圖:

sp-p-11

這里只是介紹了Grafana使用的冰山一角,更多配置和使用命令可以自行查閱它的官方文檔。

原理和擴(kuò)展

原理

下面是Prometheus的工作原理流程圖退唠,來源于其官網(wǎng):

sp-p-12

在SpringBoot項(xiàng)目中,它的工作原理如下:

sp-p-13

這就是為什么能夠使用Metrics的靜態(tài)方法直接進(jìn)行數(shù)據(jù)統(tǒng)計(jì),因?yàn)镾pring內(nèi)部用MeterRegistryPostProcessor對Metrics內(nèi)部持有的全局的CompositeMeterRegistry進(jìn)行了合成操作,也就是所有MeterRegistry類型的Bean都會添加到Metrics內(nèi)部持有的靜態(tài)globalRegistry酌泰。

擴(kuò)展

下面來個(gè)相對有生產(chǎn)意義的擴(kuò)展實(shí)現(xiàn),這篇文章提到SpringCloud體系的監(jiān)控匕累,我們需要擴(kuò)展一個(gè)功能陵刹,記錄一下每個(gè)有效的請求的執(zhí)行時(shí)間。添加下面幾個(gè)類或者方法:

//注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodMetric {

    String name() default "";

    String description() default "";

    String[] tags() default {};
}
//切面類
@Aspect
@Component
public class HttpMethodCostAspect {

    @Autowired
    private MeterRegistry meterRegistry;

    @Pointcut("@annotation(club.throwable.sample.aspect.MethodMetric)")
    public void pointcut() {
    }

    @Around(value = "pointcut()")
    public Object process(ProceedingJoinPoint joinPoint) throws Throwable {
        Method targetMethod = ((MethodSignature) joinPoint.getSignature()).getMethod();
        //這里是為了拿到實(shí)現(xiàn)類的注解
        Method currentMethod = ClassUtils.getUserClass(joinPoint.getTarget().getClass())
                .getDeclaredMethod(targetMethod.getName(), targetMethod.getParameterTypes());
        if (currentMethod.isAnnotationPresent(MethodMetric.class)) {
            MethodMetric methodMetric = currentMethod.getAnnotation(MethodMetric.class);
            return processMetric(joinPoint, currentMethod, methodMetric);
        } else {
            return joinPoint.proceed();
        }
    }

    private Object processMetric(ProceedingJoinPoint joinPoint, Method currentMethod,
                                 MethodMetric methodMetric) throws Throwable {
        String name = methodMetric.name();
        if (!StringUtils.hasText(name)) {
            name = currentMethod.getName();
        }
        String desc = methodMetric.description();
        if (!StringUtils.hasText(desc)) {
            desc = name;
        }
        String[] tags = methodMetric.tags();
        if (tags.length == 0) {
            tags = new String[2];
            tags[0] = name;
            tags[1] = name;
        }
        Timer timer = Timer.builder(name).tags(tags)
                .description(desc)
                .register(meterRegistry);
        return timer.record(() -> {
            try {
                return joinPoint.proceed();
            } catch (Throwable throwable) {
                throw new IllegalStateException(throwable);
            }
        });
    }
}
//啟動類里面添加方法
@SpringBootApplication
@EnableEurekaClient
@RestController
public class SampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(SampleApplication.class, args);
    }

    @MethodMetric
    @GetMapping(value = "/hello")
    public String hello(@RequestParam(name = "name", required = false, defaultValue = "doge") String name) {
        return String.format("%s say hello!", name);
    }
}

配置好Grafana的面板欢嘿,重啟項(xiàng)目衰琐,多次調(diào)用/hello接口:

sp-p-14

后記

如果想把監(jiān)控界面做得更炫酷、更直觀炼蹦、更詳細(xì)羡宙,可以先熟悉一下Prometheus的查詢語法和Grafana的面板配置,此外掐隐,Grafana或者Prometheus都支持預(yù)警功能辛辨,可以接入釘釘機(jī)器人等以便及時(shí)發(fā)現(xiàn)問題作出預(yù)警。

參考資料:

本文Demo項(xiàng)目倉庫:https://github.com/zjcscut/spring-cloud-prometheus-sample

(本文完)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瑟枫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子指攒,更是在濱河造成了極大的恐慌慷妙,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件允悦,死亡現(xiàn)場離奇詭異膝擂,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)隙弛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門架馋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人全闷,你說我怎么就攤上這事叉寂。” “怎么了总珠?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵屏鳍,是天一觀的道長。 經(jīng)常有香客問我局服,道長钓瞭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任淫奔,我火速辦了婚禮山涡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己鸭丛,他們只是感情好竞穷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著系吩,像睡著了一般来庭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上穿挨,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天月弛,我揣著相機(jī)與錄音,去河邊找鬼科盛。 笑死帽衙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的贞绵。 我是一名探鬼主播厉萝,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼榨崩!你這毒婦竟也來了谴垫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤母蛛,失蹤者是張志新(化名)和其女友劉穎翩剪,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體彩郊,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡前弯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了秫逝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恕出。...
    茶點(diǎn)故事閱讀 39,696評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖违帆,靈堂內(nèi)的尸體忽然破棺而出浙巫,到底是詐尸還是另有隱情,我是刑警寧澤前方,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布狈醉,位于F島的核電站,受9級特大地震影響惠险,放射性物質(zhì)發(fā)生泄漏苗傅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一班巩、第九天 我趴在偏房一處隱蔽的房頂上張望渣慕。 院中可真熱鬧嘶炭,春花似錦、人聲如沸逊桦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽强经。三九已至睡陪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間匿情,已是汗流浹背兰迫。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留炬称,地道東北人汁果。 一個(gè)月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像玲躯,于是被迫代替她去往敵國和親据德。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評論 2 353

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