天天看點

spring-boot-starter-actuator與應用監控

所有的應用開發完成之後,其最終目的都是為了上線運作,SpringBoot 應用也不例外,而在應用運作的漫長生命周期内,為了保障其可以持續穩定的服務,我們通常需要對其進行監控,進而可以了解應用的運作狀态,并根據情況決定是否需要對其運作狀态進行調整。

順應需求,SpringBoot 架構提供了 spring-boot-starter-actuator 自動配置子產品用于支援 SpringBoot 應用的監控。Actuator 這個詞即使翻譯過來也不是很容易了解(比如翻譯成“制動器;傳動裝置;執行機構”等)。

如圖 1 所示,形象的描述了 Actuator 是什麼。

spring-boot-starter-actuator與應用監控

圖 1  Sensor 和 Actuator 示意圖

為了能夠感覺應用的運作狀态,我們通常會設定一些監控名額并采集分析,這些監控名額的采集需要在應用内部設定相應的監控點,這類監控點一般隻是讀取狀态資料,我們通常稱它們為 Sensor,即中文一般稱為“傳感器”的東西。

應用的運作狀态資料通過 Sensors 采集上來之後,我們通常會有專門的系統對這些資料進行分析和判斷,一旦某個名額資料超出了預定的門檻值,這往往意味着應用的運作狀态在這個名額上出現了“不健康”的現象,我們希望對這個名額進行調整,而為了能夠執行調整,我們需要預先在應用内部設定對應的執行調整邏輯的控制器。

比如,直接關閉的開關,或者可以執行微調甚至像刹車一樣直接快速拉低某個名額值的裝置,這些控制器就稱為 Actuator。雖然我們日常天天在說“監控,監控”,但實際上“監”跟“控”是兩個概念,Sensor 更多服務于“監”的場景,而 Actuator 則服務于“控”的場景。

spring-boot-starter-actuator 自動配置子產品預設提供了很多 endpoint,雖然自動配置子產品名為 spring-boot-starter-actuator,但實際上這些 endpoint 可以按照“監”和“控”劃分為兩類:

1. Sensor 類 endpoints

名稱 說明
autoconfig 這個 endpoint 會為我們提供一份 SpringBoot 的自動配置報告,告訴我們哪些自動配置子產品生效了,以及哪些沒有生效,原因是什麼。
beans 給出目前應用的容器中所有 bean 的資訊。
configprops 對現有容器中的 ConfigurationProperties 提供的資訊進行“消毒”處理後給出彙總資訊。
info 提供目前 SpringBoot 應用的任意資訊,我們可以通過 Environment 或者 application.properties 等形式提供以 info. 為字首的任何配置項,然後 info 這個 endpoint 就會将這些配置項的值作為資訊的一部分展示出來。
health 針對目前 SpringBoot 應用的健康檢查用的 endpoint。
env 關于目前 SpringBoot 應用對應的 Environment 資訊。
metrics 目前 SprinBoot 應用的 metrics 資訊。
trace 目前 SpringBoot 應用的 trace 資訊。
mapping 如果是基于 SpringMVC 的 Web 應用,mapping 這個 endpoint 将給出 @RequestMapping 相關資訊。

2. Actuator 類 endpoints

  • shutdown:用于關閉目前 SpringBoot 應用的 endpoint。
  • dump:用于執行線程的 dump 操作。

預設情況下,除了 shutdown 這個 endpoint(因為比較危險,如果沒有安全防護,誰都可以通路它,然後關閉應用),其他 endpoints 都是預設啟用的。

生産環境下,如果沒有啟用安全防護(比如沒有依賴 spring-boot-starter-security),那麼,建議遵循 Deny By Default 原則,将所有的 endpoints 都關掉,然後根據具體情況單獨啟用某些 endpoint:

endpoints.enabled=falseendpoints.info.enabled=trueendpoints.health.enabled=true...      

所有配置項以 endpoints. 為字首,然後根據 endpoint 名稱劃分具體配置項。大部分 endpoints 都是開箱即用,但依然有些 endpoint 提供給我們進一步擴充的權利,比如健康狀态檢查相關的 endpoint(health endpoint)。

自定義應用的健康狀态檢查

應用的健康狀态檢查是很普遍的監控需求,SpringBoot 也預先通過 org.springframework.boot.actuate.autoconfigure.HealthIndicatorAutoConfiguration 為我們提供了一些常見服務的監控檢查支援,比如:

  • DataSourceHealthIndicator
  • DiskSpaceHealthIndicator
  • RedisHealthIndicator
  • SolrHealthIndicator
  • MongoHealthIndicator

如果這些預設提供的健康檢查支援依然無法滿足我們的需要,SpringBoot 還允許我們提供更多的 HealthIndicator 實作,隻要将這些 HealthIndicator 實作類注冊到 IoC 容器,SpringBoot 會自動發現并使用它們。

假設需要檢查依賴的 dubbo 服務是否處于健康狀态,我們可以實作一個 DubboHealthIndicator:

import com.alibaba.dubbo.config.spring.ReferenceBean;import com.alibaba.dubbo.rpc.service.EchoService;import org.springframework.boot.actuate.health.AbstractHealthIndicator;import org.springframework.boot.actuate.health.Health;public class DubboHealthIndicator extends AbstractHealthIndicator {    private final ReferenceBean bean;    public DubboHealthIndicator(ReferenceBean bean) {        this.bean = bean;    }    @Override    protected void doHealthCheck(Health.Builder builder) throws Exception {        builder.withDetail("interface", bean.getObjectType());        final EchoService service = (EchoService) bean.getObject();        service.$echo("hi");        builder.up();    }}      

要實作一個自定義的 HealthIndicator,一般我們不會直接實作(Implements)HealthIndicator 接口,

而是繼承 AbstractHealthIndicator:

public abstract class AbstractHealthIndicator implements HealthIndicator {    @Override    public final Health health() {        Health.Builder builder = new Health.Builder();        try {            doHealthCheck(builder);        } catch (Exception ex) {            builder.down(ex);        }        return builder.build();    }    protected abstract void doHealthCheck(Health.Builder builder)            throws Exception;}      

好處就是,我們隻需實作 doHealthCheck,在其中實作我們面向的具體服務的健康檢查邏輯就可以了,是以,在 DubboHealthIndicator 實作類中,我們通過 dubbo 架構提供的 EchoService 直接檢查相應的 dubbo 服務健康狀态即可,隻要沒有任何異常抛出,我們就認為檢查的 dubbo 服務是狀态健康的,是以,最後會通過 Health.Builder 的 up() 方法标記服務狀态為正常運作。

為了完成對 dubbo 服務的健康檢查,隻實作一個 DubboHealthIndicator 是不夠的,我們還需要将其注冊到 IoC 容器中,但是一個一個單獨注冊太費勁了,而且還要自己提供針對某個 dubbo 服務的 ReferenceBean 依賴執行個體。

是以,為了一勞永逸,也為了其他人能夠同樣友善地使用針對 dubbo 服務的健康檢查支援,我們可以在 DubboHealthIndicator 的基礎上實作一個 spring-boot-starter-dubbo-health-indicator 自動配置子產品,即:

@Configuration@ConditionalOnClass(name = { "com.alibaba.dubbo.rpc.Exporter" })public class DubboHealthIndicatorConfiguration {    @Autowired    HealthAggregator healthAggregator;    @Autowired(required = false)    Map<String, ReferenceBean> references;    @Bean    public HealthIndicator dubboHealthIndicator() {        Map<String, HealthIndicator> indicators = new HashMap<>();        for (String key : references.keySet()) {            final ReferenceBean bean = references.get(key);            indicators.put(key.startsWith("&") ? key.replaceFirst("&", "")                    : key, new DubboHealthIndicator(bean));        }        return new CompositeHealthIndicator(healthAggregator, indicators);    }}      

然後我們在 spring-boot-starter-dubbo-health-indicator 的 META-INF/spring.factories 檔案中添加如下配置:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.keevol...DubboHealthIndicatorConfiguration      

現在,釋出 spring-boot-starter-dubbo-health-indicator 并依賴它就可以自動享受到針對目前應用引用的所有 dubbo 服務進行健康檢查的服務。

那麼針對 Map<String,ReferenceBean>references 的依賴注入是從哪裡來的?

其實 Spring 架構支援依賴注入 Key 的類型為 String 的 Map,遇到這種類型的 Map 聲明(Map),Spring 架構将掃描容器中所有類型為 T 的 bean,然後以該 bean 的 name 作為 Map 的 Key,以 bean 執行個體作為對應的 Value,進而建構一個 Map 并注入到依賴處。

開放的 endpoints 才真正“有用”

不管是 spring-boot-starter-actuator 預設提供的 endpoint 實作,還是我們自己給出的 endpoint 實作,如果隻是實作了放在 SpringBoot 應用的“身體内部”,那麼它們不會發揮任何作用,隻有将它們采集的資訊暴露開放給外部監控者,或者允許外部監控者通路它們,這些 endpoints 才會真正發揮出它們的最大“功效”。

首先,spring-boot-starter-actuator 會通過 org.springframework.boot.actuate.autoconfigure.EndpointMBeanExportAutoConfiguration 

将所有的 org.springframework.boot.actuate.endpoint.Endpoint 執行個體以 JMX MBean 的形式開放給外部監控者使用。

預設情況下,這些 Endpoint 對應的 JMX MBean 會放在 org.springframework.boot 命名空間下面,不過可以通過 endpoints.jmx.domain 配置項進行更改,比如 endpoints.jmx.domain=com.keevol.management。

EndpointMBeanExportAutoConfiguration 為我們提供了一條很好的應用監控實踐之路,既然它會把所有的 org.springframework.boot.actuate.endpoint.Endpoint 執行個體都作為 JMX Mbean 開放出去,那麼,我們就可以提供一批用于某些場景下的自定義 Endpoint 實作類,比如:

public class HelloEndpoint extends AbstractEndpoint<String> {    public HelloEndpoint(String id) {        super(id, false);    }    @Override    public String invoke() {        return "Hello, SpringBoot";    }}      

然後,将像 HelloEndpoint 這樣的實作類注冊到 SpringBoot 應用的 IoC 容器,就可以擴充 SpringBoot 的 endpoints 功能了。

Endpoint 其實更适合簡單的 Sensor 場景(即用于讀取或者提供資訊),或者簡單功能的 actuator 場景(不需要行為參數),如果需要對 SpringBoot 進行更細粒度的監控,可以考慮直接使用 Spring 架構的 JMX 支援。

除了可以使用 JMX 将 spring-boot-starter-actuator 提供的(或者我們自己提供的)endpoints 開放通路,如果目前 SpringBoot 應用恰好又是一個 Web 應用。那麼,這些 endpoints 還會通過 HTTP 協定開放給外部通路,與一般的 Web 請求處理一樣,使用的也是 Web 應用使用的 HTTP 伺服器和位址端口。

因為每個 Endpoint 都有一個 id 作為唯一辨別,是以,這些 endpoints 的預設通路路徑其實就是它們的 id,比如 info 這個 endpoint 的 HTTP 通路路徑就是 /info,而 beans 這個 endpoint 的 HTTP 通路路徑則是 /beans,以此類推。

SpringBoot 允許我們通過 management. 為字首的配置項對 endpoints 的 HTTP 開放行為進行調整:

  • 使用 management.context-path=設定自定義的 endpoints 通路上下文路徑,預設直接根路徑,即 /info,/beans 等形式。
  • 使用 management.address= 配置單獨的 HTTP 服務監聽位址,比如隻允許本地通路。

management.address=127.0.0.1 使用 management.port=設定單獨的監聽端口,預設與 web 應用的對外服務端口相同。

我們可以通過 management.port=8888 将管理接口的 HTTP 對外監聽端口設定為 8888,但如果 management.port=-1,則意味着我們将關閉管理接口的 HTTP 對外服務。

JMX 和 HTTP 是 endpoints 對外開放通路最常用的方式,鑒于 Java 的序列化漏洞以及 JMX 的遠端通路防火牆問題,建議用 HTTP 并配合安全防護将 SpringBoot 應用的 endpoints 開放給外部監控者使用。

用還是不用,這是個問題

endpoints 屬于 spring-boot-starter-actuator 提供的主要功能之一,除此之外,spring-boot-starter-actuator 還提供了更多針對應用監控的支援和實作方案。

1. CrshAutoConfiguration 與 spring-boot-starter-remote-shell

spring-boot-starter-actuator 提供了基于 CRaSH(http://www.crashub.org/)的遠端 Shell(Remote Shell)支援,從筆者角度來看,這是一把雙刃劍,不建議在生産環境使用,因為提供給自己便利的同時,也為黑客朋友們提供了便利。如果實在要用,請加強安全認證和防護。

不過,這裡我們還是會為大家分析一下 spring-boot-starter-actuator 是如何提供針對 CRaSH 的支援的。

spring-boot-starter-actuator 提供了 org.springframework.boot.actuate.autoconfigure.CrshAutoConfiguration 自動配置類,該類會在 org.crsh.plugin.PluginLifeCycle 出現在 classpath 中的時候生效。

是以,隻要将 CRaSH 作為依賴加入應用的 classpath 依賴就可以了,最簡單直接的做法是讓需要啟用 CRaSH 的 SpringBoot 應用依賴 spring-boot-starter-remote-shell 自動配置子產品,spring-boot-starter-remote-shell 的主要功效就是提供了針對 CRaSH 的各項依賴。

2. SpringBoot 的 Metrics 與 Dropwizard 的 Metrics

SpringBoot 提供了一套自己的針對系統名額的度量架構,這個架構的核心設計如圖 2 所示。

spring-boot-starter-actuator與應用監控

圖 2  SpringBoot 架構的 Metrics 核心類設計示意圖

基本上,我們隻需關注 org.springframework.boot.actuate.endpoint.PublicMetrics 即可,它可以了解為提供一組 Metric 的集合,我們既可以通過 PublicMetrics 來彙總和管理 Metric,也可以通過 MetricRepository 來存儲和管理 Metric。

一旦使用了 spring-boot-starter-actuator,隻要目前 SpringBoot 應用的 ApplicationContext 中存在任何 PublicMetrics 執行個體,EndpointAutoConfiguration 就會将這些 PublicMetrics 采集彙總到一起,然後通過 MetricsEndpoint 将它們開放出去。

spring-boot-starter-actuator 提供的 org.springframework.boot.actuate.autoconfigure.PublicMetricsAutoConfiguration 預設會把一個 SystemPublicMetrics 開放出來,用于提供系統各項名額的度量和狀态采集,另外一個就是會把目前 SpringBoot 應用的 ApplicationContext 的 org.springframework.boot.actuate.metrics.repository.MetricRepository 執行個體中的所有 Metric 彙總并開放出去。

預設如果使用者沒有給出任何自定義的 MetricRepository,spring-boot-starter-actuator 會提供一個 InMemoryMetricRepository 實作,如果我們将 Dropwizard 的 Metrics 類庫作為依賴加入 classpath,那麼,Dropwizard Metrics 的 MetricRegistry 中所有的度量名額項也會通過 PublicMetrics 的形式開發暴露出來。

雖然 SpringBoot 提供的 metrics 架構也能幫助我們完成系統和應用名額的度量,但筆者更傾向于使用 Dropwizard 這種特定場景下比較完善的方案,從 metrics 的類型,到外圍系統的內建,Dropwizard metrics 都更加成熟和完備。

3. Auditing 與 Trace

SpringBoot 的 Auditing 和 Trace 支援都遵循資料/事件+Repository 的設計(如圖 3 所示)。

spring-boot-starter-actuator與應用監控

圖 3  SpringBoot 架構 Audit 和 Trace 功能支援核心類示意圖

從設計上來說是很簡單清晰的,也有很好的統一性,但實際應用過程中,我們依然會更加傾向于特定場景的方案選型,比如 Auditing。

我們可能隻是通過列印日志時候的 Logger 名稱來區分并記錄 Audit 事件,然後通過日志采集通道彙總分析就可以了,而不用非要實作一個 LogFileBasedAuditEventRepository 或者 ElasticSearchBasedAuditEventRepository 之類的實作,否則看起來難免有些“學究”氣。對于 Trace 來說也是同樣道理,我們可能直接使用完備的 APM 方案而不是單一或者少量 Trace 事件的記錄。

繼續閱讀