所有的應用開發完成之後,其最終目的都是為了上線運作,SpringBoot 應用也不例外,而在應用運作的漫長生命周期内,為了保障其可以持續穩定的服務,我們通常需要對其進行監控,進而可以了解應用的運作狀态,并根據情況決定是否需要對其運作狀态進行調整。
順應需求,SpringBoot 架構提供了 spring-boot-starter-actuator 自動配置子產品用于支援 SpringBoot 應用的監控。Actuator 這個詞即使翻譯過來也不是很容易了解(比如翻譯成“制動器;傳動裝置;執行機構”等)。
如圖 1 所示,形象的描述了 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 所示。
圖 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 所示)。
圖 3 SpringBoot 架構 Audit 和 Trace 功能支援核心類示意圖
從設計上來說是很簡單清晰的,也有很好的統一性,但實際應用過程中,我們依然會更加傾向于特定場景的方案選型,比如 Auditing。
我們可能隻是通過列印日志時候的 Logger 名稱來區分并記錄 Audit 事件,然後通過日志采集通道彙總分析就可以了,而不用非要實作一個 LogFileBasedAuditEventRepository 或者 ElasticSearchBasedAuditEventRepository 之類的實作,否則看起來難免有些“學究”氣。對于 Trace 來說也是同樣道理,我們可能直接使用完備的 APM 方案而不是單一或者少量 Trace 事件的記錄。