每天5分钟,掌握一个 SpringBoot核心知识点。大家好,我是SpringBoot指南的小坏。前两天我们聊了日志和监控,今天来点更实际的------如果你的应用"病"了,你怎么知道?知道了又该怎么办?
一、你的应用可能正在"带病工作"
先来看几个真实场景:
场景1 :
用户:"为什么我支付成功了,订单还是没生成?"
你:"数据库有点慢,重启一下就好了。"
真相:数据库连接池泄漏,已经持续3天了。
场景2 :
老板:"最近网站怎么这么卡?"
你:"服务器配置不够,加几台机器吧。"
真相:一个SQL查询没加索引,全表扫描拖慢了整个系统。
场景3 :
运维:"内存快爆了!"
你:"Java应用就是这样,重启一下就好了。"
真相:内存泄漏,每天泄漏50MB,一个月后必崩。
这些问题的共同点:应用在"带病工作",但没人知道它"病"在哪。直到用户投诉、老板发火、服务器宕机...
二、SpringBoot的"体检中心"------Actuator
SpringBoot自带了一个"体检中心",叫Actuator。你可以理解为:
- 它是你应用的私人医生
- 24小时监测健康状况
- 随时给你出体检报告
2.1 3步开启体检中心
第一步:加个"体检设备"(依赖)
<!-- 在pom.xml里加这一行 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
第二步:打开"体检开关"(配置)
# application.yml
management:
endpoints:
web:
exposure:
include: "*" # 打开所有体检项目
第三步:看看"体检报告" 启动应用,访问:http://localhost:8080/actuator/health
你会看到:
{
"status": "UP", // 健康状态:UP健康,DOWN生病
"components": {
"diskSpace": {
"status": "UP",
"details": {
"total": "500GB", // 磁盘总量
"free": "300GB", // 剩余空间
"threshold": "10GB" // 警戒线
}
},
"ping": {
"status": "UP" // 基本心跳
}
}
}
2.2 最重要的4个体检项目
- /actuator/health - 总体健康
就像量血压、测心跳 告诉你:应用还活着吗? - /actuator/info - 基本信息
就像身份证 告诉你:这是谁?什么版本?什么时候出生的? - /actuator/metrics - 性能指标
就像全面体检 告诉你:CPU高不高?内存够不够?请求多不多? - /actuator/loggers - 日志管理
就像病历本 告诉你:现在记录了什么?还能改记录级别
三、自定义体检项目:检查第三方服务
光检查自己健康还不够,还要检查你依赖的"朋友"(其他服务)是否健康。
3.1 检查数据库连接
@Component
public class DatabaseHealthCheck {
@Autowired
private DataSource dataSource;
// 这个方法会出现在/actuator/health里
public Health check() {
try {
// 尝试获取数据库连接
Connection conn = dataSource.getConnection();
// 检查连接是否有效
if (conn.isValid(2)) { // 2秒超时
return Health.up()
.withDetail("message", "数据库连接正常")
.withDetail("time", LocalDateTime.now())
.build();
} else {
return Health.down()
.withDetail("error", "数据库连接无效")
.build();
}
} catch (Exception e) {
return Health.down()
.withDetail("error", "数据库连接失败")
.withDetail("reason", e.getMessage())
.build();
}
}
}
访问/actuator/health,你会看到:
{
"status": "DOWN", // 总体不健康!
"components": {
"db": {
"status": "DOWN",
"details": {
"error": "数据库连接失败",
"reason": "Connection refused"
}
}
}
}
3.2 检查Redis是否正常
@Component
public class RedisHealthCheck {
@Autowired
private RedisTemplate<String, String> redisTemplate;
public Health check() {
try {
// 执行一个简单的PING命令
String result = redisTemplate.execute(
(RedisCallback<String>) connection ->
connection.ping()
);
if ("PONG".equals(result)) {
return Health.up()
.withDetail("message", "Redis服务正常")
.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();
}
}
}
四、可视化监控大屏:Grafana
看JSON太累?我们需要一个更直观的"体检报告大屏"。
4.1 什么是Grafana?
简单说,Grafana就是:
- 医院的体检大屏:所有指标一目了然
- 汽车的仪表盘:实时显示车速、油耗
- 应用的监控台:CPU、内存、请求量全显示
4.2 5分钟搭建监控大屏
第一步:加个"数据采集器"
<dependency>
<groupId>io.https://zhida.zhihu.com/search?content_id=268152353&content_type=Article&match_order=1&q=micrometer&zhida_source=entity</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
第二步:暴露数据接口
management:
endpoints:
web:
exposure:
include: prometheus,health,metrics
第三步:启动Grafana(Docker最简单)
# 创建一个https://zhida.zhihu.com/search?content_id=268152353&content_type=Article&match_order=1&q=docker-compose&zhida_source=entity.yml文件
version: '3'
services:
prometheus:
image: prom/prometheus
ports:
- "9090:9090"
grafana:
image: grafana/grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
# 运行
docker-compose up -d
第四步:访问大屏
- 打开 http://localhost:3000
- 用户名:admin,密码:admin
- 导入SpringBoot监控模板
你就能看到这样的酷炫大屏:
┌─────────────────────────────────────┐
│ SpringBoot应用监控 │
├─────────────────────────────────────┤
│ ✅ CPU使用率:25% │
│ ✅ 内存使用:1.2G/2G │
│ ✅ 请求量:1200次/分钟 │
│ ❌ 错误率:3.2% (偏高) │
│ ✅ 数据库连接:45/100 │
│ ✅ 响应时间:平均125ms │
└─────────────────────────────────────┘
4.3 最重要的5个监控图表
- 请求量折线图
- 看:每分钟多少请求
- 发现:流量高峰时段
- 错误率饼图
- 看:错误占比
- 发现:哪个接口出错最多
- 响应时间趋势图
- 看:接口响应时间变化
- 发现:什么时候开始变慢
- JVM内存堆栈图
- 看:内存使用情况
- 发现:是否有内存泄漏
- 数据库连接池图
- 看:连接数变化
- 发现:连接是否被耗尽
五、告警:让系统自己"喊救命"
监控有了,但总不能24小时盯着屏幕吧?你需要告警------让系统自己"喊救命"。
5.1 配置钉钉告警(最常用)
第一步:创建钉钉机器人
- 钉钉群 → 群设置 → 智能群助手 → 添加机器人
- 选择"自定义"
- 设置机器人名字,比如"系统监控机器人"
- 复制webhook地址
第二步:配置告警规则
# application.yml
management:
health:
# 设置健康检查的细节显示
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;
// 监听健康状态变化
@EventListener
public void onHealthChanged(Health health) {
if (health.getStatus() == Status.DOWN) {
// 发送钉钉告警
String message = String.format(
"【系统告警】\n" +
"应用状态:不健康\n" +
"时间:%s\n" +
"详情:%s",
LocalDateTime.now(),
health.getDetails()
);
dingTalk.sendAlert(message);
}
}
}
5.2 告警消息示例
普通告警(发到钉钉群):
【系统监控】用户服务响应时间偏高
服务:user-service
实例:192.168.1.100:8080
当前响应时间:2.1s
阈值:1.0s
时间:2024-01-15 14:30:00
建议:检查数据库索引
紧急告警(打电话):
【紧急告警】订单服务数据库连接失败!
状态:DOWN
问题:数据库连接池耗尽
影响:用户无法下单
时间:2024-01-15 14:35:00
操作:请立即重启或扩容!
5.3 告警分级策略
alert:
levels:
P0: # 最高级:必须马上处理
conditions:
- 服务完全不可用
- 数据库连接失败
- 核心业务失败率>20%
actions:
- 打电话
- 发钉钉
- 发短信
P1: # 高级:1小时内处理
conditions:
- 响应时间>3s
- 错误率>10%
- 磁盘使用率>90%
actions:
- 发钉钉
- 发邮件
P2: # 中级:今天处理
conditions:
- 内存使用率>80%
- CPU使用率>70%
- 请求量突增200%
actions:
- 发邮件
- 记录日志
P3: # 低级:本周优化
conditions:
- 慢查询数量增加
- 日志错误率<1%
- 缓存命中率下降
actions:
- 记录日志
- 周会讨论
六、实战案例:电商系统健康检查
假设你有一个电商系统,需要检查这些:
@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. 检查订单服务
details.put("orderService", checkOrderService());
// 2. 检查支付服务
details.put("paymentService", checkPaymentService());
// 3. 检查库存服务
details.put("inventoryService", checkInventoryService());
// 4. 检查缓存
details.put("redis", checkRedis());
// 5. 检查数据库
details.put("database", checkDatabase());
// 判断整体健康状态
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 {
// 模拟创建订单
Order testOrder = orderService.createTestOrder();
return Health.up()
.withDetail("message", "订单服务正常")
.withDetail("testOrderId", testOrder.getId())
.build();
} catch (Exception e) {
return Health.down()
.withDetail("error", "订单服务异常")
.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数据不一致")
.build();
}
} catch (Exception e) {
return Health.down()
.withDetail("error", "Redis连接失败")
.build();
}
}
}
访问/actuator/health,你会看到:
{
"status": "UP",
"components": {
"orderService": {
"status": "UP",
"details": {
"message": "订单服务正常",
"testOrderId": "123456"
}
},
"paymentService": {
"status": "UP",
"details": {
"message": "支付服务正常",
"responseTime": "45ms"
}
},
"redis": {
"status": "DOWN",
"details": {
"error": "Redis连接失败",
"reason": "Connection refused"
}
}
}
}
关键信息:
- 总体状态:因为Redis挂了,所以是DOWN
- 具体哪个组件挂了:Redis
- 为什么挂:Connection refused
七、避坑指南
坑1:健康检查本身把系统搞挂了
// ❌ 错误:健康检查太耗时
public Health check() {
// 执行一个10秒的SQL查询...
ResultSet rs = executeLongQuery();
return Health.up().build();
}
// ✅ 正确:设置超时时间
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:敏感信息泄露
// ❌ 错误:暴露了数据库密码
return Health.up()
.withDetail("database", "连接正常")
.withDetail("url", "jdbc:mysql://localhost:3306")
.withDetail("username", "root")
.withDetail("password", "123456") // 天啊!密码泄露了!
.build();
// ✅ 正确:只暴露必要信息
return Health.up()
.withDetail("database", "连接正常")
.withDetail("响应时间", "20ms")
.build();
坑3:告警太多,变成"狼来了"
# ❌ 错误:什么都告警
alert:
rules:
- CPU使用率 > 50% # 太敏感了!
- 内存使用率 > 60%
- 请求量增加 10%
- 响应时间 > 100ms
# ✅ 正确:只告警关键问题
alert:
rules:
- 服务不可用
- 错误率 > 5%
- 响应时间 > 3s
- 磁盘使用率 > 90%
八、最佳实践总结
8.1 健康检查配置清单
# 必须检查的项目
health:
checks:
# 系统层面
- 磁盘空间
- 内存使用
- CPU负载
# 应用层面
- 数据库连接
- Redis连接
- 消息队列
# 业务层面
- 核心API可用性
- 第三方服务
- 定时任务状态
8.2 监控告警检查清单
-
\] 监控面板能否访问?
-
\] 关键指标是否有阈值?
-
\] 是否有告警升级机制?
8.3 一个完整的配置示例
# application-prod.yml
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
base-path: /monitor # 自定义路径,更安全
endpoint:
health:
show-details: when-authorized # 只有授权用户能看到详情
probes:
enabled: true # 开启就绪和存活检查
# 安全配置
security:
enabled: true
roles: ADMIN # 需要ADMIN角色
# 指标配置
metrics:
export:
prometheus:
enabled: true
tags:
application: ${spring.application.name}
environment: prod
# 自定义健康检查
custom:
health:
# 检查频率
check-interval: 30s
# 超时时间
timeout: 5s
# 重试次数
retry-times: 3
九、今日思考题
场景:你是公司的技术负责人,需要设计一套健康检查方案:
- 给老板看什么?
- 整体系统是否健康
- 今天有多少订单
- 用户增长趋势
- 给运维看什么?
- 服务器CPU、内存
- 数据库连接数
- 网络延迟
- 给开发看什么?
- 哪个接口最慢
- 什么错误最多
- JVM内存情况