朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator

powerzhuye發表於2019-02-27

本文會來看一下Spring Boot Actuator提供給我們的監控端點Endpoint、健康檢查Health和打點指標Metrics等所謂的Production-ready(生產環境必要的一些)功能。

監控端點

我們先來新建一個模組:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>

   <groupId>me.josephzhu</groupId>
   <artifactId>spring101-ops</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>

   <name>spring101-ops</name>
   <description>Demo project for Spring Boot</description>

   <parent>
      <groupId>me.josephzhu</groupId>
      <artifactId>spring101</artifactId>
      <version>0.0.1-SNAPSHOT</version>
   </parent>


   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-data-redis</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
   </dependencies>
</project>
複製程式碼

引入了必要的actuator和web啟動器,此外,還引入了data-redis啟動器用於測試一些功能。
然後建立主程式:

package me.josephzhu.spring101ops;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@Configuration
public class Spring101OpsApplication {

   public static void main(String[] args) {

      SpringApplication.run(Spring101OpsApplication.class, args);
   }
   @Bean
   RestTemplate restTemplate(RestTemplateBuilder builder){
      return builder.build();
   }
}
複製程式碼

建立一個測試Controller:

package me.josephzhu.spring101ops;

import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@RestController
@RequestMapping("api")
public class MyController {

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @GetMapping("items")
    public List<MyItem> items(@RequestParam(value = "count",defaultValue = "10") int count){
        stringRedisTemplate.opsForValue().set("testKey", "value" + count);
        return IntStream.rangeClosed(1,count).mapToObj(i->new MyItem("name" + i,i)).collect(Collectors.toList());
    }
}
複製程式碼

這裡有用到一個MyItem:

package me.josephzhu.spring101ops;

import lombok.AllArgsConstructor;
import lombok.Data;

@AllArgsConstructor
@Data
public class MyItem {
    private String name;
    private Integer price;
}
複製程式碼

最後配置一下application.properties:

management.server.port=8081
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
複製程式碼

這裡做了幾個配置:

  1. 修改actuator的訪問埠為8081
  2. 開放所有的訪問端點,引入Spring Security後可以對暴露的斷點進行更多安全配置,生產環境不建議直接開啟所有端點
  3. 健康檢查端點顯示詳細資訊,如果不展開顯示詳細資訊,那麼只會有一個總的狀態資訊

啟動程式訪問如下地址http://localhost:8081/actuator,可以看到頁面列出了支援功能的連結,常用的有:

  1. auditevents:檢視審計事件
  2. beans:檢視Spring中的Bean清單
    朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator
  3. conditions:檢視Spring Boot自動配置匹配過程
    朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator
  4. configprops:檢視所有的配置
    朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator
  5. env:檢視Spring的ConfigurableEnvironment
    朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator
  6. health:檢視程式健康情況,後面細說
  7. httptrace:檢視HTTP請求響應明細(預設100條)
    朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator
  8. info:檢視程式資訊,後面細說
  9. loggers:檢視日誌配置,支援動態修改日誌級別
    朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator
  10. metrics:檢視所有的metrics資訊,後面細說
  11. mappings:檢視MVC的@RequestMapping配置
    朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator
  12. scheduledtasks:檢視定時任務
  13. sessions:檢視和刪除使用者session
  14. shutdown:優雅停止程式
  15. threaddump:執行緒Dump
    朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator
  16. heapdump:下載GZIP壓縮的hprof檔案
  17. logfile:檢視日誌檔案,需要先配置日誌檔案路徑
    朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator

還有一些其它的自帶的端點,這裡就不說了,比如用於資料遷移的flyway,用於給prometheus拉取metrics資料的端點,端點還可以自定義。
別小看這些功能,這些功能使得我們線上對應用程式進行運維可以很方便:

  1. 可以排查配置、環境問題
  2. 可以對執行緒Dump排查High CPU問題
  3. 可以對堆Dump排查Memory Leak問題
  4. 可以修改日誌級別,排查程式問題
  5. 可以檢視定時任務、RequestMapping等資訊
  6. 可以讓負載均衡器有統一的埠檢查應用狀態
  7. 可以暴露、收集、彙總程式內部的各種指標用於繪製監控皮膚
  8. 其它各種程式內部的資訊

健康檢查

先來訪問/actuator/health重點看一下健康檢查:

朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator

這裡可以看到不但Spring Boot幫我們自動配置收集了redis和磁碟的監控資訊,而且我們還自定義了一個叫做myService的檢查項。這裡因為我們程式有使用到Redis,所以自動配置了相應的檢查,自動支援自動配置的HealthIndicator有下面這些(基本各種Spring Data支援的資料來源都有了):

朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator

下面我們看一下如何自定義監控檢查項:

package me.josephzhu.spring101ops;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@Component
public class MyServiceHealthIndicator implements HealthIndicator {
    @Autowired
    private RestTemplate restTemplate;

    @Override
    public Health health() {
        try {
            ResponseEntity<List<MyItem>> responseEntity =
                    restTemplate.exchange("http://localhost:8080/api/items",
                            HttpMethod.GET, null, new ParameterizedTypeReference<List<MyItem>>() {});
            if (responseEntity.getStatusCode().is2xxSuccessful())
                return Health.up().build();
            else
                return Health.down().status(responseEntity.getStatusCode().toString()).build();

        } catch (Exception ex) {
            return Health.down(ex).build();
        }
    }
}
複製程式碼

實現很簡單,實現HealthIndicator介面即可,我們的類是XXHealthIndicator,那麼自動就會加入一個叫XX的項。在這裡,我們訪問了遠端的一個服務,當服務出現非2XX的響應或呼叫服務出現異常的時候,我們認為這個服務的健康是DOWN的,否則就是UP狀態。在down的時候我們可以傳入狀態、異常等補充資訊,比如這是一個DOWN的情況:

朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator

因為某一項DOWN了,整個應用的狀態認為是DOWN。
之前也說過,如果每一個程式都有統一的health入口可以監控程式狀態的話,我們的負載均衡就可以依靠這個來做應用的故障下線。之所以我們需要定義自己的HealthIndicator加入是因為很多時候程式啟動成功並不代表程式正常工作,程式是否真正工作正常往往依賴於外部的一些關鍵服務和內部的一些關鍵元件(執行緒池、佇列等)。

應用資訊

在本節中,我們來看一下如何進行簡單配置實現暴露如下應用資訊的功能:

  1. 通過配置方式暴露自定義的資訊
  2. 通過程式方式暴露自定義的資訊
  3. 暴露應用構建資訊
  4. 暴露應用git版本資訊
    訪問/actuator/info可以看到下面的資訊:
    朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator

    在截圖中第一項可以看到app這個JSON暴露了一些自定義的資訊,我們可以在application.properties配置檔案中加入這樣的配置來實現:

info.app.name=Test SpringBoot Actuator
info.app.description=Test how to configure Health/Info/Metrics etc. with Spring Boot Actuator
info.app.version=1.0.1
info.app.author=JosephZhu
複製程式碼

截圖中第二項可以看到git這個JSON暴露了程式git相關的資訊,實現方式是加入一個外掛到pom中:

<plugin>
   <groupId>pl.project13.maven</groupId>
   <artifactId>git-commit-id-plugin</artifactId>
   <version>2.1.15</version>
</plugin>
複製程式碼

此外,如果需要檢視git詳細資訊的話需要加入如下配置到配置檔案:

management.info.git.mode=full
複製程式碼

截圖中第三項可以看到build這個JSON暴露了程式構建一些資訊,這個通過Spring Boot的maven外掛就可以實現,加入build-info這個Goal來生成:

<plugin>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-maven-plugin</artifactId>
   <executions>
      <execution>
         <goals>
            <goal>build-info</goal>
         </goals>
      </execution>
   </executions>
</plugin>
複製程式碼

我們使用maven執行一下這兩個外掛,可以看到編譯後多了build-info.properties和git.properties,這也就是Actuator獲取資訊的來源:

朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator

截圖中最後我們看到有一個key項的資訊,這是我們通過程式定義的,實現方式是實現InfoContributor介面:

package me.josephzhu.spring101ops;

import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.stereotype.Component;

@Component
public class MyInfoContributor implements InfoContributor {
    @Override
    public void contribute(Info.Builder builder) {
        builder.withDetail("key","value").build();
    }
}
複製程式碼

對外暴露一些程式內部的重要資訊往往也是很重要排查線上問題的手段。Info適合展示不太會變動的一些複雜資訊,如果希望整個歷史狀態資訊都能保留和監控的話更適合採用下面的打點方式。

監控打點

訪問/actuator/metrics可以看到下面的資訊:

朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator

Spring Boot整合了一個叫做MicroMeter的庫用於Metrics。這個庫號稱是Metrics界的SLF4J,它的作用在於:

  1. 可以適配多達10+的監控資料庫,包括Graphite、Influx、StatsD等等。
  2. 以統一的語言定義了打點這個事情。
  3. 自動整合了很多JVM的資訊,包括記憶體、GC、CPU、執行緒等。
    我們可以隨便點一個Metrics進去檢視:
    朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator

    下面,我們來看一下如何通過簡單的幾個配置實現把所有的打點資訊傳送到InfluxDb中去。
    第一步,在pom中引入influx依賴:

<dependency>
   <groupId>io.micrometer</groupId>
   <artifactId>micrometer-registry-influx</artifactId>
</dependency>
複製程式碼

當然,需要先在本機安裝一下influxdb,MacOS也就是一行命令,brew install influxdb即可。
第二步,加入配置:

management.metrics.web.server.auto-time-requests=true
management.metrics.export.influx.enabled=true
management.metrics.export.influx.auto-create-db=true
management.metrics.export.influx.db=myapp
management.metrics.export.influx.step=10s
複製程式碼

逐一說明一下這些配置:

  1. 第一個配置用於自動開啟所有Web請求的執行時間記錄,如果不這麼配的話可以手動在Controller上加@Timed註解
  2. 第二個配置用於開啟micrometer的influx的支援
  3. 第三個配置用於自動建立influxdb的資料庫
  4. 第四個配置指定了influxdb的資料庫名稱為myapp
  5. 第五個配置指定了彙總上報資料到influxdb的週期

我們可以啟動程式,多訪問幾次http://localhost:8080/api/items,然後開啟http://localhost:8081/actuator/metrics/http.server.requests檢視,可以看到具體請求的執行次數和執行時間(證明1):

朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator

同時,我們可以在日誌中看到(證明5):

2018-10-08 20:49:41.429  INFO 16390 --- [pool-1-thread-1] i.micrometer.influx.InfluxMeterRegistry  : successfully sent 73 metrics to influx
2018-10-08 20:49:51.483  INFO 16390 --- [pool-1-thread-1] i.micrometer.influx.InfluxMeterRegistry  : successfully sent 73 metrics to influx
複製程式碼

有73個監控指標每隔10秒傳送到一次influxdb,我們可以進入influx客戶端,檢視這個measurement的資訊(先輸入命令use myapp):

朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator

還記得嗎,我們實現了一個自定義的HealthIndicator,裡面有用到RestTemplate來訪問遠端服務,現在可以嘗試多訪問幾次http://localhost:8081/actuator/health,然後開啟http://localhost:8081/actuator/metrics/http.client.requests可以看到的確有資訊:

朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator

說明RestTemplat預設為我們整合了Metrics,記錄客戶端HTTP請求的執行情況。
資料收集和上發的工作完成了,最後我們可以brew install grafana,使用Grafna連線InfluxDb來配置一下我們的監控皮膚。
首先配置一下資料來源:

朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator

然後就可以配置圖表了:

朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator

有關Grafana的使用方式這裡不詳述了。

自定義監控打點

我們再來看一下如何自定義Metrics項實現自己的打點,修改一下我們的Controller:

@Autowired
MeterRegistry meterRegistry;

@GetMapping("items")
public List<MyItem> items(@RequestParam(value = "count",defaultValue = "10") int count){
    stringRedisTemplate.opsForValue().set("testKey", "value" + count);

    meterRegistry.timer("mytimer").record(()-> {
        try {
            Thread.sleep(count);
        } catch (InterruptedException e) {
        }
    });
    meterRegistry.counter("mycounter").increment(count);
    meterRegistry.gauge("currentValue1", count);

    return IntStream.rangeClosed(1,count).mapToObj(i->new MyItem("name" + i,i)).collect(Collectors.toList());
}
複製程式碼

在這裡,我們注入了MeterRegistry類,然後通過這個類我們可以方便進行各種資訊的上報。在MicroMeter中,資訊抽象為了這麼幾種:

  1. 狀態資訊,所謂狀態資訊就是每次上報的是資訊的當前值,資料是不能累計的。比如當前執行緒數48,1分鐘後是50,這個值如果進行彙總聚合的話沒有太大意義(總計98個執行緒?平均49個執行緒?),在展現的時候只能展現某個時刻的某個值。
    朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator
  2. 數量資訊,比如請求執行的次數,一次上報可以+1也可以+N,這個資料是可以聚合的。比如如果我們在1分鐘內上報了2次,一次10,一次20,那麼1分鐘的聚合就是總數30,每次上報記錄的是聚合的總和資訊。
    朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator
  3. 執行時間資訊,可以方便的統計方法執行時間。上報的資訊會比數量資訊豐富,除了次數之外還有總執行時間、平均執行時間、最長執行時間。
    朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator

    是不是很方便,使用MicroMeter統一的API,我們只要根據需要收集指標資料即可,剩下的資料整理、彙總以及和後端資料庫互動上報資料的過程都自動完成。
    最後,我們再來看一個把/actuator/health的資訊轉換成打點上報的例子,我們可以通過獲得所有的HealthIndicator,交由CompositeHealthIndicator彙總健康資訊結果,然後通過MeterRegistry上報監控資訊的打點:

package me.josephzhu.spring101ops;

import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.context.annotation.Configuration;

import java.util.List;

import static java.util.Collections.emptyList;

@Configuration
class HealthMetricsConfiguration {
    private CompositeHealthIndicator compositeHealthIndicator;

    public HealthMetricsConfiguration(HealthAggregator healthAggregator,
                                      List<HealthIndicator> healthIndicators,
                                      MeterRegistry registry) {

        compositeHealthIndicator = new CompositeHealthIndicator(healthAggregator);

        for (Integer i = 0; i < healthIndicators.size(); i++) {
            compositeHealthIndicator.addHealthIndicator(i.toString(), healthIndicators.get(i));
        }

        registry.gauge("health", emptyList(), compositeHealthIndicator, health -> {
            Status status = health.health().getStatus();
            switch (status.getCode()) {
                case "UP":
                    return 3;
                case "OUT_OF_SERVICE":
                    return 2;
                case "DOWN":
                    return 1;
                case "UNKNOWN":
                default:
                    return 0;
            }
        });
    }
}
複製程式碼

重啟應用後稍等一下,看一下InfluxDb中的資料,的確是一些值為3的記錄,代表UP:

朱曄和你聊Spring系列S1E7:簡單好用的Spring Boot Actuator

總結

本文我們通過一些例子覆蓋瞭如下內容:

  1. Actuator模組的功能。
  2. 詳述了健康檢查端點,自定義健康檢查項。
  3. 詳述了資料資訊端點,為程式新增各種附加資料項。
  4. 詳述了監控打點,程式方式上報自定義的三種形式的打點。
  5. InfluxDb和Grafana的簡單使用。

的確,每一個功能都是簡單的幾步配置,Spring Boot Actuator真的很方便,這些功能是一個真正的可用於生產環境的程式必不可少的一些功能,Spring Boot不僅僅為我們提供了方便,而且為我們定義了架構模板,讓每一個開發人員都能有意識,應該做一些什麼,這也就是我為什麼一直說Spring引領了企業級單機開發,Spring Boot引領了網際網路微服務開發。

但是,Spring Boot因為在高速發展,會不斷吸收好的開源專案整合到生態中去,所以在API上變化會較多,API一直在修改,增加了不少學習成本和坑。任何事情都有兩面性,我們在要求Spring生態為我們提供越來越多功能的時候,享受到便利的同時,也必須去適應Spring的快速變化。

老樣子,本系列文章程式碼見我的github:github.com/JosephZhu19…

相關文章