SpringBoot實(shí)現(xiàn)健康檢查的完整指南
每天5分鐘,掌握一個 SpringBoot核心知識點(diǎn)。大家好,我是SpringBoot指南的小壞。前兩天我們聊了日志和監(jiān)控,今天來點(diǎn)更實(shí)際的——如果你的應(yīng)用”病”了,你怎么知道?知道了又該怎么辦?
一、你的應(yīng)用可能正在”帶病工作”
先來看幾個真實(shí)場景:
場景1:
用戶:”為什么我支付成功了,訂單還是沒生成?”
你:”數(shù)據(jù)庫有點(diǎn)慢,重啟一下就好了。”
真相:數(shù)據(jù)庫連接池泄漏,已經(jīng)持續(xù)3天了。
場景2:
老板:”最近網(wǎng)站怎么這么卡?”
你:”服務(wù)器配置不夠,加幾臺機(jī)器吧。”
真相:一個SQL查詢沒加索引,全表掃描拖慢了整個系統(tǒng)。
場景3:
運(yùn)維:”內(nèi)存快爆了!”
你:”Java應(yīng)用就是這樣,重啟一下就好了。”
真相:內(nèi)存泄漏,每天泄漏50MB,一個月后必崩。
這些問題的共同點(diǎn):應(yīng)用在”帶病工作”,但沒人知道它”病”在哪。直到用戶投訴、老板發(fā)火、服務(wù)器宕機(jī)…
二、SpringBoot的”體檢中心”——Actuator
SpringBoot自帶了一個”體檢中心”,叫Actuator。你可以理解為:
- 它是你應(yīng)用的私人醫(yī)生
- 24小時監(jiān)測健康狀況
- 隨時給你出體檢報告
2.1 3步開啟體檢中心
第一步:加個”體檢設(shè)備”(依賴)
<!-- 在pom.xml里加這一行 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>第二步:打開”體檢開關(guān)”(配置)
# application.yml
management:
endpoints:
web:
exposure:
include: "*" # 打開所有體檢項(xiàng)目第三步:看看”體檢報告” 啟動應(yīng)用,訪問:http://localhost:8080/actuator/health
你會看到:
{
"status": "UP", // 健康狀態(tài):UP健康,DOWN生病
"components": {
"diskSpace": {
"status": "UP",
"details": {
"total": "500GB", // 磁盤總量
"free": "300GB", // 剩余空間
"threshold": "10GB" // 警戒線
}
},
"ping": {
"status": "UP" // 基本心跳
}
}
}2.2 最重要的4個體檢項(xiàng)目
- /actuator/health - 總體健康
就像量血壓、測心跳 告訴你:應(yīng)用還活著嗎? - /actuator/info - 基本信息
就像身份證 告訴你:這是誰?什么版本?什么時候出生的? - /actuator/metrics - 性能指標(biāo)
就像全面體檢 告訴你:CPU高不高?內(nèi)存夠不夠?請求多不多? - /actuator/loggers - 日志管理
就像病歷本 告訴你:現(xiàn)在記錄了什么?還能改記錄級別
三、自定義體檢項(xiàng)目:檢查第三方服務(wù)
光檢查自己健康還不夠,還要檢查你依賴的”朋友”(其他服務(wù))是否健康。
3.1 檢查數(shù)據(jù)庫連接
@Component
public class DatabaseHealthCheck {
@Autowired
private DataSource dataSource;
// 這個方法會出現(xiàn)在/actuator/health里
public Health check() {
try {
// 嘗試獲取數(shù)據(jù)庫連接
Connection conn = dataSource.getConnection();
// 檢查連接是否有效
if (conn.isValid(2)) { // 2秒超時
return Health.up()
.withDetail("message", "數(shù)據(jù)庫連接正常")
.withDetail("time", LocalDateTime.now())
.build();
} else {
return Health.down()
.withDetail("error", "數(shù)據(jù)庫連接無效")
.build();
}
} catch (Exception e) {
return Health.down()
.withDetail("error", "數(shù)據(jù)庫連接失敗")
.withDetail("reason", e.getMessage())
.build();
}
}
}訪問/actuator/health,你會看到:
{
"status": "DOWN", // 總體不健康!
"components": {
"db": {
"status": "DOWN",
"details": {
"error": "數(shù)據(jù)庫連接失敗",
"reason": "Connection refused"
}
}
}
}3.2 檢查Redis是否正常
@Component
public class RedisHealthCheck {
@Autowired
private RedisTemplate<String, String> redisTemplate;
public Health check() {
try {
// 執(zhí)行一個簡單的PING命令
String result = redisTemplate.execute(
(RedisCallback<String>) connection ->
connection.ping()
);
if ("PONG".equals(result)) {
return Health.up()
.withDetail("message", "Redis服務(wù)正常")
.build();
} else {
return Health.down()
.withDetail("error", "Redis返回異常")
.build();
}
} catch (Exception e) {
return Health.down()
.withDetail("error", "Redis連接失敗")
.withDetail("reason", e.getMessage())
.build();
}
}
}3.3 檢查第三方API
@Component
public class ThirdPartyHealthCheck {
@Autowired
private RestTemplate restTemplate;
public Health check() {
// 檢查支付接口
Health paymentHealth = checkPaymentService();
// 檢查短信接口
Health smsHealth = checkSmsService();
// 如果有一個不健康,總體就不健康
if (paymentHealth.getStatus() == Status.DOWN ||
smsHealth.getStatus() == Status.DOWN) {
return Health.down()
.withDetail("payment", paymentHealth)
.withDetail("sms", smsHealth)
.build();
}
return Health.up()
.withDetail("payment", paymentHealth)
.withDetail("sms", smsHealth)
.build();
}
private Health checkPaymentService() {
try {
ResponseEntity<String> response = restTemplate.getForEntity(
"https://payment.api.com/health",
String.class
);
if (response.getStatusCode().is2xxSuccessful()) {
return Health.up().build();
} else {
return Health.down()
.withDetail("status", response.getStatusCodeValue())
.build();
}
} catch (Exception e) {
return Health.down()
.withDetail("error", e.getMessage())
.build();
}
}
}四、可視化監(jiān)控大屏:Grafana
看JSON太累?我們需要一個更直觀的”體檢報告大屏”。
4.1 什么是Grafana?
簡單說,Grafana就是:
- 醫(yī)院的體檢大屏:所有指標(biāo)一目了然
- 汽車的儀表盤:實(shí)時顯示車速、油耗
- 應(yīng)用的監(jiān)控臺:CPU、內(nèi)存、請求量全顯示
4.2 5分鐘搭建監(jiān)控大屏
第一步:加個”數(shù)據(jù)采集器”
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>第二步:暴露數(shù)據(jù)接口
management:
endpoints:
web:
exposure:
include: prometheus,health,metrics第三步:啟動Grafana(Docker最簡單)
# 創(chuàng)建一個docker-compose.yml文件
version: '3'
services:
prometheus:
image: prom/prometheus
ports:
- "9090:9090"
grafana:
image: grafana/grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
# 運(yùn)行
docker-compose up -d第四步:訪問大屏
- 打開 http://localhost:3000
- 用戶名:admin,密碼:admin
- 導(dǎo)入SpringBoot監(jiān)控模板
你就能看到這樣的酷炫大屏:
┌─────────────────────────────────────┐ │ SpringBoot應(yīng)用監(jiān)控 │ ├─────────────────────────────────────┤ │ ? CPU使用率:25% │ │ ? 內(nèi)存使用:1.2G/2G │ │ ? 請求量:1200次/分鐘 │ │ ? 錯誤率:3.2% (偏高) │ │ ? 數(shù)據(jù)庫連接:45/100 │ │ ? 響應(yīng)時間:平均125ms │ └─────────────────────────────────────┘
4.3 最重要的5個監(jiān)控圖表
- 請求量折線圖
- 看:每分鐘多少請求
- 發(fā)現(xiàn):流量高峰時段
- 錯誤率餅圖
- 看:錯誤占比
- 發(fā)現(xiàn):哪個接口出錯最多
- 響應(yīng)時間趨勢圖
- 看:接口響應(yīng)時間變化
- 發(fā)現(xiàn):什么時候開始變慢
- JVM內(nèi)存堆棧圖
- 看:內(nèi)存使用情況
- 發(fā)現(xiàn):是否有內(nèi)存泄漏
- 數(shù)據(jù)庫連接池圖
- 看:連接數(shù)變化
- 發(fā)現(xiàn):連接是否被耗盡
五、告警:讓系統(tǒng)自己”喊救命”
監(jiān)控有了,但總不能24小時盯著屏幕吧?你需要告警——讓系統(tǒng)自己”喊救命”。
5.1 配置釘釘告警(最常用)
第一步:創(chuàng)建釘釘機(jī)器人
- 釘釘群 → 群設(shè)置 → 智能群助手 → 添加機(jī)器人
- 選擇”自定義”
- 設(shè)置機(jī)器人名字,比如”系統(tǒng)監(jiān)控機(jī)器人”
- 復(fù)制webhook地址
第二步:配置告警規(guī)則
# application.yml
management:
health:
# 設(shè)置健康檢查的細(xì)節(jié)顯示
show-details: always
# 釘釘告警配置
endpoint:
health:
enabled: true
alert:
dingtalk:
webhook: https://oapi.dingtalk.com/robot/send?access_token=你的token第三步:寫告警代碼
@Component
public class HealthAlert {
@Autowired
private DingTalkService dingTalk;
// 監(jiān)聽健康狀態(tài)變化
@EventListener
public void onHealthChanged(Health health) {
if (health.getStatus() == Status.DOWN) {
// 發(fā)送釘釘告警
String message = String.format(
"【系統(tǒng)告警】\n" +
"應(yīng)用狀態(tài):不健康\n" +
"時間:%s\n" +
"詳情:%s",
LocalDateTime.now(),
health.getDetails()
);
dingTalk.sendAlert(message);
}
}
}5.2 告警消息示例
普通告警(發(fā)到釘釘群):
【系統(tǒng)監(jiān)控】用戶服務(wù)響應(yīng)時間偏高 服務(wù):user-service 實(shí)例:192.168.1.100:8080 當(dāng)前響應(yīng)時間:2.1s 閾值:1.0s 時間:2024-01-15 14:30:00 建議:檢查數(shù)據(jù)庫索引
緊急告警(打電話):
【緊急告警】訂單服務(wù)數(shù)據(jù)庫連接失?。? 狀態(tài):DOWN 問題:數(shù)據(jù)庫連接池耗盡 影響:用戶無法下單 時間:2024-01-15 14:35:00 操作:請立即重啟或擴(kuò)容!
5.3 告警分級策略
alert:
levels:
P0: # 最高級:必須馬上處理
conditions:
- 服務(wù)完全不可用
- 數(shù)據(jù)庫連接失敗
- 核心業(yè)務(wù)失敗率>20%
actions:
- 打電話
- 發(fā)釘釘
- 發(fā)短信
P1: # 高級:1小時內(nèi)處理
conditions:
- 響應(yīng)時間>3s
- 錯誤率>10%
- 磁盤使用率>90%
actions:
- 發(fā)釘釘
- 發(fā)郵件
P2: # 中級:今天處理
conditions:
- 內(nèi)存使用率>80%
- CPU使用率>70%
- 請求量突增200%
actions:
- 發(fā)郵件
- 記錄日志
P3: # 低級:本周優(yōu)化
conditions:
- 慢查詢數(shù)量增加
- 日志錯誤率<1%
- 緩存命中率下降
actions:
- 記錄日志
- 周會討論六、實(shí)戰(zhàn)案例:電商系統(tǒng)健康檢查
假設(shè)你有一個電商系統(tǒng),需要檢查這些:
@Component
public class EcommerceHealthCheck {
@Autowired
private OrderService orderService;
@Autowired
private PaymentService paymentService;
@Autowired
private InventoryService inventoryService;
@Autowired
private RedisTemplate<String, String> redisTemplate;
public Health check() {
Map<String, Health> details = new HashMap<>();
// 1. 檢查訂單服務(wù)
details.put("orderService", checkOrderService());
// 2. 檢查支付服務(wù)
details.put("paymentService", checkPaymentService());
// 3. 檢查庫存服務(wù)
details.put("inventoryService", checkInventoryService());
// 4. 檢查緩存
details.put("redis", checkRedis());
// 5. 檢查數(shù)據(jù)庫
details.put("database", checkDatabase());
// 判斷整體健康狀態(tài)
boolean allUp = details.values().stream()
.allMatch(h -> h.getStatus() == Status.UP);
if (allUp) {
return Health.up()
.withDetails(details)
.build();
} else {
return Health.down()
.withDetails(details)
.build();
}
}
private Health checkOrderService() {
try {
// 模擬創(chuàng)建訂單
Order testOrder = orderService.createTestOrder();
return Health.up()
.withDetail("message", "訂單服務(wù)正常")
.withDetail("testOrderId", testOrder.getId())
.build();
} catch (Exception e) {
return Health.down()
.withDetail("error", "訂單服務(wù)異常")
.withDetail("reason", e.getMessage())
.build();
}
}
private Health checkRedis() {
try {
// 測試Redis連接和性能
long start = System.currentTimeMillis();
redisTemplate.opsForValue().set("health_check", "test");
String value = redisTemplate.opsForValue().get("health_check");
long cost = System.currentTimeMillis() - start;
if ("test".equals(value)) {
return Health.up()
.withDetail("message", "Redis正常")
.withDetail("responseTime", cost + "ms")
.build();
} else {
return Health.down()
.withDetail("error", "Redis數(shù)據(jù)不一致")
.build();
}
} catch (Exception e) {
return Health.down()
.withDetail("error", "Redis連接失敗")
.build();
}
}
}訪問/actuator/health,你會看到:
{
"status": "UP",
"components": {
"orderService": {
"status": "UP",
"details": {
"message": "訂單服務(wù)正常",
"testOrderId": "123456"
}
},
"paymentService": {
"status": "UP",
"details": {
"message": "支付服務(wù)正常",
"responseTime": "45ms"
}
},
"redis": {
"status": "DOWN",
"details": {
"error": "Redis連接失敗",
"reason": "Connection refused"
}
}
}
}關(guān)鍵信息:
- 總體狀態(tài):因?yàn)镽edis掛了,所以是DOWN
- 具體哪個組件掛了:Redis
- 為什么掛:Connection refused
七、避坑指南
坑1:健康檢查本身把系統(tǒng)搞掛了
// ? 錯誤:健康檢查太耗時
public Health check() {
// 執(zhí)行一個10秒的SQL查詢...
ResultSet rs = executeLongQuery();
return Health.up().build();
}
// ? 正確:設(shè)置超時時間
public Health check() {
try {
Future<Boolean> future = executor.submit(() -> {
return checkDatabase();
});
// 最多等2秒
boolean healthy = future.get(2, TimeUnit.SECONDS);
return healthy ? Health.up() : Health.down();
} catch (TimeoutException e) {
return Health.down()
.withDetail("error", "健康檢查超時")
.build();
}
}坑2:敏感信息泄露
// ? 錯誤:暴露了數(shù)據(jù)庫密碼
return Health.up()
.withDetail("database", "連接正常")
.withDetail("url", "jdbc:mysql://localhost:3306")
.withDetail("username", "root")
.withDetail("password", "123456") // 天??!密碼泄露了!
.build();
// ? 正確:只暴露必要信息
return Health.up()
.withDetail("database", "連接正常")
.withDetail("響應(yīng)時間", "20ms")
.build();坑3:告警太多,變成”狼來了”
# ? 錯誤:什么都告警
alert:
rules:
- CPU使用率 > 50% # 太敏感了!
- 內(nèi)存使用率 > 60%
- 請求量增加 10%
- 響應(yīng)時間 > 100ms
# ? 正確:只告警關(guān)鍵問題
alert:
rules:
- 服務(wù)不可用
- 錯誤率 > 5%
- 響應(yīng)時間 > 3s
- 磁盤使用率 > 90%八、最佳實(shí)踐總結(jié)
8.1 健康檢查配置清單
# 必須檢查的項(xiàng)目
health:
checks:
# 系統(tǒng)層面
- 磁盤空間
- 內(nèi)存使用
- CPU負(fù)載
# 應(yīng)用層面
- 數(shù)據(jù)庫連接
- Redis連接
- 消息隊(duì)列
# 業(yè)務(wù)層面
- 核心API可用性
- 第三方服務(wù)
- 定時任務(wù)狀態(tài)8.2 監(jiān)控告警檢查清單
- [ ] 監(jiān)控面板能否訪問?
- [ ] 告警是否正常工作?
- [ ] 關(guān)鍵指標(biāo)是否有閾值?
- [ ] 告警聯(lián)系人是否正確?
- [ ] 是否有告警升級機(jī)制?
- [ ] 告警歷史是否可追溯?
8.3 一個完整的配置示例
# application-prod.yml
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
base-path: /monitor # 自定義路徑,更安全
endpoint:
health:
show-details: when-authorized # 只有授權(quán)用戶能看到詳情
probes:
enabled: true # 開啟就緒和存活檢查
# 安全配置
security:
enabled: true
roles: ADMIN # 需要ADMIN角色
# 指標(biāo)配置
metrics:
export:
prometheus:
enabled: true
tags:
application: ${spring.application.name}
environment: prod
# 自定義健康檢查
custom:
health:
# 檢查頻率
check-interval: 30s
# 超時時間
timeout: 5s
# 重試次數(shù)
retry-times: 3九、今日思考題
場景:你是公司的技術(shù)負(fù)責(zé)人,需要設(shè)計一套健康檢查方案:
- 給老板看什么?
- 整體系統(tǒng)是否健康
- 今天有多少訂單
- 用戶增長趨勢
- 給運(yùn)維看什么?
- 服務(wù)器CPU、內(nèi)存
- 數(shù)據(jù)庫連接數(shù)
- 網(wǎng)絡(luò)延遲
- 給開發(fā)看什么?
- 哪個接口最慢
- 什么錯誤最多
- JVM內(nèi)存情況
以上就是SpringBoot實(shí)現(xiàn)健康檢查的完整指南的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot健康檢查的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
微服務(wù)mybatis?typehandler使用詳解(就這一篇夠了)
TypeHandler是MyBatis框架的核心組件,實(shí)現(xiàn)數(shù)據(jù)庫表字段類型和Java?數(shù)據(jù)類型之間的相互轉(zhuǎn)換,本文介紹通過實(shí)例代碼mybatis?typehandler使用,感興趣的朋友一起看看吧2024-02-02
Elasticsearch term 查詢之精確值搜索功能實(shí)現(xiàn)
term查詢是Elasticsearch中用于精確值搜索的一種基本方式,通過了解 term 查詢的工作原理和使用方法,你可以更好地利用 Elasticsearch 進(jìn)行結(jié)構(gòu)化數(shù)據(jù)的搜索和分析,本文將詳細(xì)介紹 term 查詢的工作原理、使用場景以及如何在 Elasticsearch 中應(yīng)用它,感興趣的朋友一起看看吧2024-06-06
IDEA安裝部署Alibaba Cloud Toolkit的實(shí)現(xiàn)步驟
Alibaba Cloud Toolkit是阿里云針對IDE平臺為開發(fā)者提供的一款插件,本文主要介紹了IDEA安裝部署Alibaba Cloud Toolkit的實(shí)現(xiàn)步驟,具有一定的參考價值,感興趣的可以了解一下2023-08-08
Java9新特性Stream流API優(yōu)化與增強(qiáng)
這篇文章主要為大家介紹了Java9新特性Stream流API優(yōu)化與增強(qiáng)的用法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助祝大家多多進(jìn)步,早日升職加薪2022-03-03
spring boot請求異常處理并返回對應(yīng)的html頁面
這篇文章主要介紹了spring boot處理請求異常并返回對應(yīng)的html頁面,包括404異常處理和500異常處理,需要的朋友可以參考下2017-07-07
java面試題解LeetCode27二叉樹的鏡像實(shí)例
這篇文章主要為大家介紹了java面試題解LeetCode27二叉樹的鏡像實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01

