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

閱讀PDF版本java

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

監控端點

咱們先來新建一個模塊:github

<?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啓動器用於測試一些功能。
而後建立主程序:web

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:redis

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:spring

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

這裏作了幾個配置:apache

  1. 修改actuator的訪問端口爲8081
  2. 開放全部的訪問端點,引入Spring Security後能夠對暴露的斷點進行更多安全配置,生產環境不建議直接開啓全部端點
  3. 健康檢查端點顯示詳細信息,若是不展開顯示詳細信息,那麼只會有一個總的狀態信息

啓動程序訪問以下地址http://localhost:8081/actuator,能夠看到頁面列出了支持功能的連接,經常使用的有:後端

  1. auditevents:查看審計事件
  2. beans:查看Spring中的Bean清單
  3. conditions:查看Spring Boot自動配置匹配過程
  4. configprops:查看全部的配置
  5. env:查看Spring的ConfigurableEnvironment
  6. health:查看程序健康狀況,後面細說
  7. httptrace:查看HTTP請求響應明細(默認100條)
  8. info:查看程序信息,後面細說
  9. loggers:查看日誌配置,支持動態修改日誌級別
  10. metrics:查看全部的metrics信息,後面細說
  11. mappings:查看MVC的@RequestMapping配置
  12. scheduledtasks:查看定時任務
  13. sessions:查看和刪除用戶session
  14. shutdown:優雅中止程序
  15. threaddump:線程Dump
  16. heapdump:下載GZIP壓縮的hprof文件
  17. logfile:查看日誌文件,須要先配置日誌文件路徑

還有一些其它的自帶的端點,這裏就不說了,好比用於數據遷移的flyway,用於給prometheus拉取metrics數據的端點,端點還能夠自定義。
別小看這些功能,這些功能使得咱們線上對應用程序進行運維能夠很方便:api

  1. 能夠排查配置、環境問題
  2. 能夠對線程Dump排查High CPU問題
  3. 能夠對堆Dump排查Memory Leak問題
  4. 能夠修改日誌級別,排查程序問題
  5. 能夠查看定時任務、RequestMapping等信息
  6. 可讓負載均衡器有統一的端口檢查應用狀態
  7. 能夠暴露、收集、彙總程序內部的各類指標用於繪製監控面板
  8. 其它各類程序內部的信息

健康檢查

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

這裏能夠看到不但Spring Boot幫咱們自動配置收集了redis和磁盤的監控信息,並且咱們還自定義了一個叫作myService的檢查項。這裏由於咱們程序有使用到Redis,因此自動配置了相應的檢查,自動支持自動配置的HealthIndicator有下面這些(基本各類Spring Data支持的數據源都有了):

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

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的狀況:

由於某一項DOWN了,整個應用的狀態認爲是DOWN。
以前也說過,若是每個程序都有統一的health入口能夠監控程序狀態的話,咱們的負載均衡就能夠依靠這個來作應用的故障下線。之因此咱們須要定義本身的HealthIndicator加入是由於不少時候程序啓動成功並不表明程序正常工做,程序是否真正工做正常每每依賴於外部的一些關鍵服務和內部的一些關鍵組件(線程池、隊列等)。

應用信息

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

  1. 經過配置方式暴露自定義的信息
  2. 經過程序方式暴露自定義的信息
  3. 暴露應用構建信息
  4. 暴露應用git版本信息
    訪問/actuator/info能夠看到下面的信息:

    在截圖中第一項能夠看到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獲取信息的來源:

截圖中最後咱們看到有一個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 Boot集成了一個叫作MicroMeter的庫用於Metrics。這個庫號稱是Metrics界的SLF4J,它的做用在於:

  1. 能夠適配多達10+的監控數據庫,包括Graphite、Influx、StatsD等等。
  2. 以統一的語言定義了打點這個事情。
  3. 自動集成了不少JVM的信息,包括內存、GC、CPU、線程等。
    咱們能夠隨便點一個Metrics進去查看:

    下面,咱們來看一下如何經過簡單的幾個配置實現把全部的打點信息發送到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):

同時,咱們能夠在日誌中看到(證實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):

還記得嗎,咱們實現了一個自定義的HealthIndicator,裏面有用到RestTemplate來訪問遠程服務,如今能夠嘗試多訪問幾回http://localhost:8081/actuator/health,而後打開http://localhost:8081/actuator/metrics/http.client.requests能夠看到的確有信息:

說明RestTemplat默認爲咱們集成了Metrics,記錄客戶端HTTP請求的執行狀況。
數據收集和上發的工做完成了,最後咱們能夠brew install grafana,使用Grafna鏈接InfluxDb來配置一下咱們的監控面板。
首先配置一下數據源:

而後就能夠配置圖表了:

有關Grafna的使用方式這裏不詳述了。

自定義監控打點

咱們再來看一下如何自定義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個線程?),在展示的時候只能展示某個時刻的某個值。
  2. 數量信息,好比請求執行的次數,一次上報能夠+1也能夠+N,這個數據是能夠聚合的。好比若是咱們在1分鐘內上報了2次,一次10,一次20,那麼1分鐘的聚合就是總數30,每次上報記錄的是聚合的總和信息。
  3. 執行時間信息,能夠方便的統計方法執行時間。上報的信息會比數量信息豐富,除了次數以外還有總執行時間、平均執行時間、最長執行時間。

    是否是很方便,使用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:

總結

本文咱們經過一些例子覆蓋了以下內容:

  1. Actuator模塊的功能。
  2. 詳述了健康檢查端點,自定義健康檢查項。
  3. 詳述了數據信息端點,爲程序添加各類附加數據項。
  4. 詳述了監控打點,程序方式上報自定義的三種形式的打點。
  5. InfluxDb和Grafana的簡單使用。

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

可是,Spring Boot由於在高速發展,會不斷吸取好的開源項目整合到生態中去,因此在API上變化會較多,API一直在修改,增長了很多學習成本和坑。任何事情都有兩面性,咱們在要求Spring生態爲咱們提供愈來愈多功能的時候,享受到便利的同時,也必須去適應Spring的快速變化。

老樣子,本系列文章代碼見個人github:https://github.com/JosephZhu1983/Spring101。

相關文章
相關標籤/搜索