SpringBoot實(shí)現(xiàn)系統(tǒng)監(jiān)控的完整示例
一、系統(tǒng)監(jiān)控:你的應(yīng)用需要“體檢醫(yī)生”
想象一下:你的SpringBoot應(yīng)用就像一個996的程序員,每天24小時不間斷工作。某天它突然“猝死”了,而你卻不知道它是何時倒下、為何倒下、倒下前有沒有喊“救命”... 這就是沒有監(jiān)控的可怕之處!
系統(tǒng)監(jiān)控就像是:
- 健康手環(huán):時刻監(jiān)測應(yīng)用的心跳(健康狀態(tài))
- 行車記錄儀:記錄每一次接口調(diào)用的“交通事故”
- 私人醫(yī)生:自動診斷哪里出了問題,還能開藥方(告警)
- 行為分析師:分析用戶的“奇怪”操作模式
二、監(jiān)控大作戰(zhàn)詳細(xì)步驟
第1步:基礎(chǔ)依賴 - 準(zhǔn)備“體檢工具箱”
<!-- 監(jiān)控全家桶套餐 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 健康檢查加強(qiáng)版 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 可視化監(jiān)控面板 -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>2.7.0</version>
</dependency>
<!-- 監(jiān)控指標(biāo)導(dǎo)出到Prometheus -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
第2步:配置監(jiān)控權(quán)限 - 設(shè)置“診室門禁”
# application.yml
spring:
application:
name: my-application # 給應(yīng)用起個名字,方便認(rèn)領(lǐng)
management:
endpoints:
web:
exposure:
include: "*" # 開放所有監(jiān)控端點(diǎn)
exclude: env,beans # 敏感信息
endpoint:
health:
show-details: always # 健康檢查顯示詳細(xì)信息
probes:
enabled: true # 開啟K8s探針支持
prometheus:
enabled: true # 開啟Prometheus指標(biāo)導(dǎo)出
metrics:
export:
prometheus:
enabled: true
tags:
application: ${spring.application.name} # 給指標(biāo)打標(biāo)簽
info:
env:
enabled: true # 顯示環(huán)境信息
# 自定義健康檢查項(xiàng)
info:
app:
name: "超級無敵SpringBoot應(yīng)用"
version: "v2.0.0"
description: "這是一個被嚴(yán)密監(jiān)控的應(yīng)用,別想偷懶!"
developer: "監(jiān)控狂魔團(tuán)隊(duì)"
motto: "沒有監(jiān)控的代碼就是在裸奔"
第3步:自定義健康檢查 - 編寫“體檢項(xiàng)目”
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
import java.util.Random;
/**
* 數(shù)據(jù)庫連接健康檢查
* 相當(dāng)于檢查應(yīng)用的“消化系統(tǒng)”
*/
@Component("database")
public class DatabaseHealthIndicator implements HealthIndicator {
private final Random random = new Random();
@Override
public Health health() {
boolean isHealthy = checkDatabaseConnection();
if (isHealthy) {
return Health.up()
.withDetail("message", "數(shù)據(jù)庫連接正常,可以愉快地CRUD啦!")
.withDetail("responseTime", "15ms")
.withDetail("activeConnections", 25)
.build();
} else {
return Health.down()
.withDetail("message", "數(shù)據(jù)庫連接失敗,快去檢查水管(連接)!")
.withDetail("errorCode", "DB_CONN_001")
.withDetail("suggestion", "試試重啟大法?")
.build();
}
}
private boolean checkDatabaseConnection() {
// 模擬檢查,真實(shí)項(xiàng)目應(yīng)該真的ping一下數(shù)據(jù)庫
return random.nextInt(100) > 10; // 90%的概率健康
}
}
/**
* 緩存健康檢查
* 相當(dāng)于檢查應(yīng)用的“短期記憶”
*/
@Component("cache")
public class CacheHealthIndicator implements HealthIndicator {
@Override
public Health health() {
return Health.up()
.withDetail("status", "緩存運(yùn)行正常")
.withDetail("memoryUsage", "45%")
.withDetail("hitRate", "89.7%")
.withDetail("motto", "記不住的東西,交給緩存吧!")
.build();
}
}
/**
* 外部API依賴檢查
* 相當(dāng)于檢查應(yīng)用的“社交能力”
*/
@Component("externalApi")
public class ExternalApiHealthIndicator implements HealthIndicator {
@Override
public Health health() {
// 模擬檢查第三方API
boolean wechatApi = checkWechatApi();
boolean paymentApi = checkPaymentApi();
if (wechatApi && paymentApi) {
return Health.up()
.withDetail("wechat", "微信API:連接正常,可以愉快聊天")
.withDetail("payment", "支付API:錢包鼓鼓,隨時收錢")
.withDetail("overall", "所有外部API都很給力!")
.build();
} else {
return Health.down()
.withDetail("wechat", wechatApi ? "正常" : "失聯(lián)中...")
.withDetail("payment", paymentApi ? "正常" : "掉線了,沒法收錢!")
.withDetail("emergency", "快聯(lián)系第三方客服!")
.build();
}
}
private boolean checkWechatApi() {
return true; // 假裝檢查
}
private boolean checkPaymentApi() {
return new Random().nextInt(100) > 5; // 95%可用率
}
}
第4步:自定義監(jiān)控指標(biāo) - 安裝“行為記錄儀”
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 業(yè)務(wù)指標(biāo)監(jiān)控
* 相當(dāng)于給應(yīng)用安裝“計(jì)步器”和“心率監(jiān)測”
*/
@Component
public class BusinessMetrics {
// 用戶注冊計(jì)數(shù)器(統(tǒng)計(jì)有多少人“入坑”)
private final Counter userRegistrationCounter;
// 訂單創(chuàng)建計(jì)數(shù)器(統(tǒng)計(jì)賺了多少錢)
private final Counter orderCreationCounter;
// 異常計(jì)數(shù)器(統(tǒng)計(jì)出了多少bug)
private final Counter exceptionCounter;
// 活躍用戶數(shù)(看看多少人在“玩”)
private final AtomicInteger activeUsers;
// API響應(yīng)時間計(jì)時器(看看接口是不是“拖延癥”)
private final Timer apiResponseTimer;
// 自定義業(yè)務(wù)指標(biāo)(比如:點(diǎn)贊數(shù)、評論數(shù)等)
private final Counter likeCounter;
public BusinessMetrics(MeterRegistry registry) {
// 初始化所有指標(biāo)
userRegistrationCounter = Counter.builder("user.registration.total")
.description("用戶注冊總數(shù)")
.tag("application", "my-app")
.register(registry);
orderCreationCounter = Counter.builder("order.created.total")
.description("訂單創(chuàng)建總數(shù)")
.tag("application", "my-app")
.register(registry);
exceptionCounter = Counter.builder("exception.total")
.description("異常發(fā)生總數(shù)")
.tag("application", "my-app")
.register(registry);
activeUsers = registry.gauge("user.active.current",
new AtomicInteger(0));
apiResponseTimer = Timer.builder("api.response.time")
.description("API響應(yīng)時間")
.publishPercentiles(0.5, 0.95, 0.99) // 統(tǒng)計(jì)50%, 95%, 99%分位
.register(registry);
likeCounter = Counter.builder("content.like.total")
.description("內(nèi)容點(diǎn)贊總數(shù)")
.tag("application", "my-app")
.register(registry);
}
// 用戶注冊時調(diào)用(又有人“入坑”啦!)
public void incrementUserRegistration() {
userRegistrationCounter.increment();
System.out.println("恭喜!又有新用戶注冊啦!當(dāng)前總數(shù): " +
userRegistrationCounter.count());
}
// 訂單創(chuàng)建時調(diào)用(錢錢錢?。?
public void incrementOrderCreation(double amount) {
orderCreationCounter.increment();
System.out.println("收到新訂單!金額: " + amount +
",訂單總數(shù): " + orderCreationCounter.count());
}
// 異常發(fā)生時調(diào)用(啊哦,出bug了)
public void incrementException(String exceptionType) {
exceptionCounter.increment();
System.err.println("發(fā)現(xiàn)bug!類型: " + exceptionType +
",bug總數(shù): " + exceptionCounter.count());
}
// 更新活躍用戶數(shù)(看看誰在摸魚)
public void setActiveUsers(int count) {
activeUsers.set(count);
}
// 記錄API響應(yīng)時間(看看哪個接口在偷懶)
public void recordApiTime(long millis, String apiName) {
apiResponseTimer.record(millis, TimeUnit.MILLISECONDS);
if (millis > 1000) {
System.out.println("警告!接口 " + apiName +
" 響應(yīng)緩慢,耗時: " + millis + "ms");
}
}
// 點(diǎn)贊時調(diào)用(收到小心心)
public void incrementLike() {
likeCounter.increment();
System.out.println("收到一個點(diǎn)贊!當(dāng)前點(diǎn)贊數(shù): " + likeCounter.count());
}
}
第5步:AOP監(jiān)控切面 - 安裝“全方位攝像頭”
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 接口監(jiān)控切面
* 相當(dāng)于在每個接口門口安裝“攝像頭+計(jì)時器”
*/
@Aspect
@Component
public class MonitorAspect {
@Autowired
private BusinessMetrics businessMetrics;
// 監(jiān)控所有Controller(重點(diǎn)監(jiān)控對象)
@Pointcut("execution(* com.example.controller..*.*(..))")
public void controllerPointcut() {}
// 監(jiān)控Service層(業(yè)務(wù)邏輯層)
@Pointcut("execution(* com.example.service..*.*(..))")
public void servicePointcut() {}
@Around("controllerPointcut()")
public Object monitorController(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
String methodName = joinPoint.getSignature().toShortString();
System.out.println("?? 接口開始執(zhí)行: " + methodName);
try {
Object result = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - startTime;
// 記錄響應(yīng)時間
businessMetrics.recordApiTime(executionTime, methodName);
System.out.println("接口執(zhí)行成功: " + methodName +
",耗時: " + executionTime + "ms");
return result;
} catch (Exception e) {
// 記錄異常
businessMetrics.incrementException(e.getClass().getSimpleName());
System.err.println("接口執(zhí)行失敗: " + methodName +
",異常: " + e.getMessage());
throw e;
}
}
@Around("servicePointcut()")
public Object monitorService(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().toShortString();
// 檢查是不是重要業(yè)務(wù)方法
if (methodName.contains("UserService.register")) {
System.out.println("開始注冊用戶...");
} else if (methodName.contains("OrderService.create")) {
System.out.println("開始創(chuàng)建訂單...");
}
try {
return joinPoint.proceed();
} catch (Exception e) {
System.err.println("業(yè)務(wù)方法出錯: " + methodName);
businessMetrics.incrementException("ServiceException");
throw e;
}
}
}
第6步:監(jiān)控告警配置 - 設(shè)置“緊急呼叫按鈕”
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* 監(jiān)控告警系統(tǒng)
* 相當(dāng)于應(yīng)用的“私人醫(yī)生緊急呼叫”
*/
@Component
public class MonitorAlert {
@Autowired(required = false)
private JavaMailSender mailSender;
@Autowired
private BusinessMetrics businessMetrics;
private HealthIndicator databaseHealth;
// 每小時檢查一次健康狀態(tài)(定期體檢)
@Scheduled(fixedRate = 60 * 60 * 1000)
public void healthCheck() {
System.out.println("開始定時健康檢查...");
// 檢查異常數(shù)(看看是不是bug泛濫)
double exceptionCount = businessMetrics.getExceptionCount();
if (exceptionCount > 100) {
sendAlert("異常數(shù)超標(biāo)警告",
"系統(tǒng)異常數(shù)已達(dá)到 " + exceptionCount + ",快去修bug!");
}
// 檢查響應(yīng)時間(看看接口是不是在偷懶)
double avgResponseTime = businessMetrics.getAvgResponseTime();
if (avgResponseTime > 5000) {
sendAlert("系統(tǒng)響應(yīng)緩慢",
"平均響應(yīng)時間 " + avgResponseTime + "ms,用戶要跑光了!");
}
System.out.println("健康檢查完成,一切正常!");
}
// 每天發(fā)送日報(bào)(每日病情報(bào)告)
@Scheduled(cron = "0 0 18 * * ?") // 每天下午6點(diǎn)
public void dailyReport() {
String report = buildDailyReport();
System.out.println("生成日報(bào):\n" + report);
if (mailSender != null) {
sendEmail("系統(tǒng)監(jiān)控日報(bào) - " + new Date(), report);
}
}
private String buildDailyReport() {
return """
======== 系統(tǒng)監(jiān)控日報(bào) ========
日期:%s
今日新增用戶:%d
今日訂單總數(shù):%d
今日點(diǎn)贊數(shù):%d
今日異常數(shù):%d
平均響應(yīng)時間:%.2f ms
系統(tǒng)健康狀態(tài):%s
============================
""".formatted(
new Date(),
businessMetrics.getTodayUserRegistrations(),
businessMetrics.getTodayOrders(),
businessMetrics.getTodayLikes(),
businessMetrics.getTodayExceptions(),
businessMetrics.getAvgResponseTime(),
getOverallHealthStatus()
);
}
private void sendAlert(String title, String message) {
System.err.println(title + ": " + message);
// 發(fā)送郵件告警(如果配置了郵件)
if (mailSender != null) {
sendEmail("[緊急]" + title, message);
}
// 這里還可以集成釘釘、企業(yè)微信、短信等告警
System.out.println("已發(fā)送告警通知!");
}
private void sendEmail(String subject, String text) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo("dev-team@example.com");
message.setSubject(subject);
message.setText(text);
mailSender.send(message);
}
private String getOverallHealthStatus() {
// 綜合判斷系統(tǒng)健康狀況
if (businessMetrics.getTodayExceptions() > 50) {
return "生病了,需要緊急處理";
} else if (businessMetrics.getAvgResponseTime() > 1000) {
return "有點(diǎn)疲憊,需要優(yōu)化";
} else {
return "非常健康,繼續(xù)努力";
}
}
}
第7步:可視化監(jiān)控面板 - 打造“監(jiān)控指揮中心”
import de.codecentric.boot.admin.server.config.EnableAdminServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Spring Boot Admin Server
* 監(jiān)控可視化面板,相當(dāng)于“監(jiān)控指揮中心大屏幕”
*/
@SpringBootApplication
@EnableAdminServer
public class MonitorDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(MonitorDashboardApplication.class, args);
System.out.println("""
監(jiān)控面板啟動成功!
訪問地址: http://localhost:8080
用戶名: admin
密碼: admin
在這里你可以看到:
1. 所有服務(wù)的健康狀態(tài)
2. 實(shí)時性能指標(biāo)
3. 告警信息
4. 日志查看
5. 動態(tài)配置管理
就像有了一個全天候的監(jiān)控室!
""");
}
}
三、訪問監(jiān)控端點(diǎn)
應(yīng)用啟動后,可以訪問以下監(jiān)控端點(diǎn):
健康檢查:http://localhost:8080/actuator/health 所有指標(biāo):http://localhost:8080/actuator/metrics Prometheus格式:http://localhost:8080/actuator/prometheus 應(yīng)用信息:http://localhost:8080/actuator/info 環(huán)境變量:http://localhost:8080/actuator/env Bean列表:http://localhost:8080/actuator/beans 映射關(guān)系:http://localhost:8080/actuator/mappings 配置屬性:http://localhost:8080/actuator/configprops
四、完整配置示例
# application-monitor.yml
spring:
boot:
admin:
client:
url: http://localhost:8080
instance:
name: ${spring.application.name}
security:
user:
name: admin
password: admin123
mail:
host: smtp.example.com
port: 587
username: monitor@example.com
password: yourpassword
properties:
mail:
smtp:
auth: true
starttls:
enable: true
# 監(jiān)控告警規(guī)則
monitor:
alert:
enabled: true
email:
recipients:
- dev-team@example.com
- ops-team@example.com
thresholds:
response-time: 5000 # 5秒
error-rate: 0.05 # 5%
memory-usage: 0.8 # 80%
schedules:
daily-report: "0 0 18 * * ?" # 每天18點(diǎn)
health-check: "0 */30 * * * ?" # 每30分鐘
五、總結(jié):監(jiān)控讓系統(tǒng)“透明化”
為什么要監(jiān)控?(監(jiān)控的五大好處)
- 預(yù)防勝于治療
- 在用戶投訴前發(fā)現(xiàn)問題
- 在系統(tǒng)崩潰前預(yù)警
- 就像給系統(tǒng)打了“疫苗”
- 快速定位問題
- 不再是“猜謎游戲”
- 精準(zhǔn)定位問題根因
- 減少“重啟試試”的次數(shù)
- 性能優(yōu)化依據(jù)
- 知道哪里慢,才知道怎么優(yōu)化
- 數(shù)據(jù)驅(qū)動的優(yōu)化決策
- 告別“我感覺這個接口有點(diǎn)慢”
- 容量規(guī)劃參考
- 知道系統(tǒng)能承受多少壓力
- 合理規(guī)劃服務(wù)器資源
- 避免“雙十一”時系統(tǒng)崩潰
- 提升開發(fā)體驗(yàn)
- 實(shí)時了解系統(tǒng)狀態(tài)
- 快速驗(yàn)證修復(fù)效果
- 減少半夜被叫起來修bug的概率
監(jiān)控的最佳實(shí)踐
- 分層監(jiān)控:從基礎(chǔ)設(shè)施到應(yīng)用層全鏈路監(jiān)控
- 關(guān)鍵指標(biāo):監(jiān)控黃金指標(biāo)(延遲、流量、錯誤、飽和度)
- 智能告警:避免告警疲勞,設(shè)置合理的閾值
- 可視化:一圖勝千言,好的圖表讓人秒懂狀態(tài)
- 持續(xù)改進(jìn):根據(jù)監(jiān)控?cái)?shù)據(jù)不斷優(yōu)化系統(tǒng)
最后
沒有監(jiān)控的系統(tǒng)就像沒有儀表的飛機(jī)——飛得高不高、快不快、安不安全,全憑感覺!等到墜機(jī)了,都不知道是沒油了還是引擎壞了。
給你的SpringBoot應(yīng)用裝上監(jiān)控,就像:
- 給汽車裝上了儀表盤+行車記錄儀+胎壓監(jiān)測
- 給程序員配上了智能手環(huán)+年度體檢+健康教練
- 給房子安裝了煙霧報(bào)警器+攝像頭+智能門鎖
監(jiān)控不是萬能的,但沒有監(jiān)控是萬萬不能的!
現(xiàn)在,去給你的應(yīng)用裝上“健康手環(huán)”,讓它從此過上“透明、健康、可觀測”的幸福生活吧!
溫馨提示:監(jiān)控雖好,也不要過度監(jiān)控哦!畢竟,連上廁所都要統(tǒng)計(jì)時間和次數(shù)的話,系統(tǒng)也會感到“壓力山大”的!
以上就是SpringBoot實(shí)現(xiàn)系統(tǒng)監(jiān)控的完整示例的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot系統(tǒng)監(jiān)控的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
logback的isDebugEnabled日志配置級別源碼解析
這篇文章主要為大家介紹了logback的isDebugEnabled日志配置級別源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
Java線程池隊(duì)列LinkedBlockingDeque
這篇文章主要為大家介紹了Java線程池隊(duì)列LinkedBlockingDeque示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
MyBatis-Plus實(shí)現(xiàn)邏輯刪除功能解析
這篇文章主要介紹了MyBatis-Plus實(shí)現(xiàn)邏輯刪除功能解析,有時候并不需要真正的刪除數(shù)據(jù),而是想邏輯刪除,方便數(shù)據(jù)恢復(fù),MyBatis-Plus可以很方便的實(shí)現(xiàn)邏輯刪除的功能,需要的朋友可以參考下2023-11-11
Spring關(guān)于@Configuration配置處理流程
這篇文章主要介紹了Spring關(guān)于@Configuration配置處理流程,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-06-06
RocketMQ生產(chǎn)者一個應(yīng)用不能發(fā)送多個NameServer消息解決
這篇文章主要為大家介紹了RocketMQ生產(chǎn)者一個應(yīng)用不能發(fā)送多個NameServer消息原因及解決方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11

