1. 概述
Spring Boot 提供了一些用於檢查正在運行應用程序及其組件的狀態和健康狀況的不同方式。 在這些方法中,<em><a href="https://docs.spring.io/spring-boot/docs/2.3.0.RELEASE/api/org/springframework/boot/actuate/health/HealthContributor.html">HealthContributor</a> </em> 和HealthIndicator API 是兩個重要的 API。
在本文中,我們將熟悉這些 API,學習它們的工作原理,以及我們如何向它們貢獻自定義信息。
2. 依賴項
健康信息貢獻者是 Spring Boot Actuator 模塊的一部分,因此我們需要合適的 Maven 依賴項:Maven 依賴項。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>3. 內置的 HealthIndicators
默認情況下,Spring Boot 會註冊許多 HealthIndicators,用於報告特定應用程序方面的健康狀況。
其中一些指標幾乎總是註冊的,例如 DiskSpaceHealthIndicator 或 PingHealthIndicator。前者報告磁盤的當前狀態,後者則作為應用程序的 ping 端點。
另一方面,Spring Boot 也會條件註冊一些指標。也就是説,如果某些依賴項在 classpath 上,或者滿足其他條件時,Spring Boot 可能會註冊一些其他的 HealthIndicators。例如,如果我們使用關係型數據庫,那麼 Spring Boot 會註冊 DataSourceHealthIndicator。 類似地,如果恰好使用 Cassandra 作為數據存儲,它也會註冊 CassandraHealthIndicator。
為了檢查 Spring Boot 應用程序的健康狀態,我們可以調用 /actuator/health 端點。此端點將報告所有已註冊的 HealthIndicators 的聚合結果。
此外,要查看特定指標的健康報告,我們可以調用 /actuator/health/{name} 端點。例如,調用 /actuator/health/diskSpace 端點將返回 DiskSpaceHealthIndicator 的狀態報告:
{
"status": "UP",
"details": {
"total": 499963170816,
"free": 134414831616,
"threshold": 10485760,
"exists": true
}
}4. 自定義HealthIndicators
除了內置的健康檢查之外,我們還可以註冊自定義的HealthIndicators,用於報告組件或子系統的健康狀況。 要做到這一點,唯一需要做的事情就是將HealthIndicator接口的實現註冊為Spring Bean。
例如,以下實現會隨機報告故障:
@Component
public class RandomHealthIndicator implements HealthIndicator {
@Override
public Health health() {
double chance = ThreadLocalRandom.current().nextDouble();
Health.Builder status = Health.up();
if (chance > 0.9) {
status = Health.down();
}
return status.build();
}
}
根據該指標的健康報告,該應用程序應僅在 90% 的時間運行。我們這裏使用 Health 構建器來報告健康信息。
在反應式應用程序中,我們應註冊類型為 ReactiveHealthIndicator 的 Bean。反應式的 health() 方法返回一個 Mono<Health> 而不是簡單的 Health。
除此之外,對於兩種 Web 應用程序類型,其他細節保持不變。
4.1 指標名稱
要查看該指標的報告,我們可以調用 /actuator/health/random</em/> 端點。例如,API 響應可能如下所示:
{"status": "UP"}該 random 在 /actuator/health/random URL 中的標識符是該指標的標識符。 特定 HealthIndicator 實現的標識符等於 Bean 名稱,但不包括 HealthIndicator 後綴。由於 Bean 名稱是 randomHealthIdenticator,因此 random 前綴將是標識符。
使用此算法,如果將 Bean 名稱更改為 rand,則指標標識符將為 rand,而不是 random:
@Component("rand")
public class RandomHealthIndicator implements HealthIndicator {
// omitted
}4.2. 禁用指示器
要禁用特定的指示器,我們可以將 management.health.<indicator_identifier>.enabled 配置屬性設置為 false。 例如,如果我們在 application.properties 中添加以下內容,則 Spring Boot 將禁用 RandomHealthIndicator:
management.health.random.enabled=false為了激活此配置屬性,我們還應在指示器上添加 @ConditionalOnEnabledHealthIndicator 註解:
@Component
@ConditionalOnEnabledHealthIndicator("random")
public class RandomHealthIndicator implements HealthIndicator {
// omitted
}現在如果調用 /actuator/health/random,Spring Boot 將返回一個 404 Not Found HTTP 響應:
@SpringBootTest
@AutoConfigureMockMvc
@TestPropertySource(properties = "management.health.random.enabled=false")
class DisabledRandomHealthIndicatorIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Test
void givenADisabledIndicator_whenSendingRequest_thenReturns404() throws Exception {
mockMvc.perform(get("/actuator/health/random"))
.andExpect(status().isNotFound());
}
}請注意,禁用內置或自定義指示器也採用類似流程。因此,我們也可以將相同的配置應用於內置指示器。
4.3. 補充詳情
除了報告狀態之外,我們還可以使用 <a href="https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/actuate/health/Health.Builder.html#withDetail-java.lang.String-java.lang.Object-"><em withDetail(key, value)</em></a> 方法添加額外的鍵值對詳情。
public Health health() {
double chance = ThreadLocalRandom.current().nextDouble();
Health.Builder status = Health.up();
if (chance > 0.9) {
status = Health.down();
}
return status
.withDetail("chance", chance)
.withDetail("strategy", "thread-local")
.build();
}我們在此向狀態報告中添加了兩件信息。通過將一個 Map<String, Object>傳遞給 withDetails(map)方法,可以實現相同的結果:
Map<String, Object> details = new HashMap<>();
details.put("chance", chance);
details.put("strategy", "thread-local");
return status.withDetails(details).build();現在如果我們調用 /actuator/health/random,我們可能會看到類似的內容:
{
"status": "DOWN",
"details": {
"chance": 0.9883560157173152,
"strategy": "thread-local"
}
}我們可以通過自動化測試來驗證這種行為:
mockMvc.perform(get("/actuator/health/random"))
.andExpect(jsonPath("$.status").exists())
.andExpect(jsonPath("$.details.strategy").value("thread-local"))
.andExpect(jsonPath("$.details.chance").exists());當與系統組件(如數據庫或磁盤)進行通信時,有時會發生異常。我們可以使用 `withException(ex)方法來報告這些異常:
if (chance > 0.9) {
status.withException(new RuntimeException("Bad luck"));
}我們還可以將異常傳遞到我們之前見過的 <em><a href="https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/actuate/health/Health.Builder.html#down-java.lang.Throwable-">down(ex)</a></em> 方法:
if (chance > 0.9) {
status = Health.down(new RuntimeException("Bad Luck"));
}現在健康報告將包含堆棧跟蹤信息。
{
"status": "DOWN",
"details": {
"error": "java.lang.RuntimeException: Bad Luck",
"chance": 0.9603739107139401,
"strategy": "thread-local"
}
}4.4. 詳細信息暴露
管理端點健康檢查的 management.endpoint.health.show-details 配置屬性控制每個健康端點可以暴露的詳細程度。
例如,如果我們將此屬性設置為 always,則 Spring Boot 將始終在健康報告中返回 details 字段,就像上面的示例一樣。
另一方面,如果我們將此屬性設置為 never,則 Spring Boot 將始終從輸出中省略 details。 此外,when_authorized 值僅對授權用户暴露額外的 details。 用户在以下情況下被授權:
- 他們已進行身份驗證
- 他們擁有 management.endpoint.health.roles 配置屬性中指定的角色
4.5. 健康狀態
默認情況下,Spring Boot 定義了四種不同的健康狀態值:Status:
- UP — 組件或子系統正在正常工作
- DOWN — 組件未正常工作
- OUT_OF_SERVICE — 組件暫時處於停用狀態
- UNKNOWN — 組件狀態未知
這些狀態被聲明為public static final實例,而不是Java枚舉,因此我們可以定義自己的自定義健康狀態。要做到這一點,可以使用status(name)方法:
Health.Builder warning = Health.status("WARNING");健康狀態會影響健康端點的 HTTP 狀態碼。 默認情況下,Spring Boot 將 DOWN 和 OUT_OF_SERVICE 狀態映射為拋出 503 狀態碼。 另一方面,UP 和任何未映射的狀態將轉換為 200 OK 狀態碼。
要自定義此映射,我們可以設置 配置屬性為所需的 HTTP 狀態碼數字:
management.endpoint.health.status.http-mapping.down=500
management.endpoint.health.status.http-mapping.out_of_service=503
management.endpoint.health.status.http-mapping.warning=500Spring Boot 將以下狀態映射為 500、OUT_OF_SERVICE 為 503,以及 WARNING 為 500 HTTP 狀態碼:
mockMvc.perform(get("/actuator/health/warning"))
.andExpect(jsonPath("$.status").value("WARNING"))
.andExpect(status().isInternalServerError());同樣,我們也可以註冊類型為`HttpCodeStatusMapper的Bean,以自定義HTTP狀態碼映射。
@Component
public class CustomStatusCodeMapper implements HttpCodeStatusMapper {
@Override
public int getStatusCode(Status status) {
if (status == Status.DOWN) {
return 500;
}
if (status == Status.OUT_OF_SERVICE) {
return 503;
}
if (status == Status.UNKNOWN) {
return 500;
}
return 200;
}
}
getStatusCode(status)方法接受健康狀態作為輸入,並返回HTTP狀態碼作為輸出。 還可以將自定義Status實例映射:
if (status.getCode().equals("WARNING")) {
return 500;
}默認情況下,Spring Boot 會註冊此接口的一個簡單實現,並使用默認映射。`SimpleHttpCodeStatusMapper 也能從配置文件的映射中讀取映射,正如我們之前所見。
5. 健康信息與指標
非平凡的應用通常包含多個不同的組件。例如,考慮使用Cassandra作為數據庫、Apache Kafka作為發佈-訂閲平台以及Hazelcast作為內存數據網格的Spring Boot應用程序。
我們應該使用HealthIndicator來確定應用程序是否能夠與這些組件進行通信。如果通信鏈路失敗,或者組件本身宕機或速度緩慢,則我們應該意識到該組件不健康。換句話説,這些指示器應該用於報告不同組件或子系統的不健康狀態。
相反,我們不應該使用HealthIndicator來衡量值、計數事件或測量持續時間。這就是為什麼我們有指標。簡單來説,指標是報告CPU使用率、負載平均值、堆大小、HTTP響應分佈等更好的工具。
6. 結論
在本文中,我們學習瞭如何向 actuator 健康端點貢獻更多健康信息。此外,我們深入探討了健康 API 的各個組件,例如 Health、Status 以及 HTTP 狀態映射的狀態。
為了總結一下,我們簡要討論了健康信息和指標之間的區別,以及何時使用它們。