SpringBoot健康检查完整指南,避免线上事故

每天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个体检项目

  1. /actuator/health - 总体健康
    就像量血压、测心跳 告诉你:应用还活着吗?
  2. /actuator/info - 基本信息
    就像身份证 告诉你:这是谁?什么版本?什么时候出生的?
  3. /actuator/metrics - 性能指标
    就像全面体检 告诉你:CPU高不高?内存够不够?请求多不多?
  4. /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

第四步:访问大屏

  1. 打开 http://localhost:3000
  2. 用户名:admin,密码:admin
  3. 导入SpringBoot监控模板

你就能看到这样的酷炫大屏:

复制代码
┌─────────────────────────────────────┐
│  SpringBoot应用监控                │
├─────────────────────────────────────┤
│  ✅ CPU使用率:25%                │
│  ✅ 内存使用:1.2G/2G             │
│  ✅ 请求量:1200次/分钟           │
│  ❌ 错误率:3.2% (偏高)           │
│  ✅ 数据库连接:45/100            │
│  ✅ 响应时间:平均125ms           │
└─────────────────────────────────────┘

4.3 最重要的5个监控图表

  1. 请求量折线图
  • 看:每分钟多少请求
  • 发现:流量高峰时段
  1. 错误率饼图
  • 看:错误占比
  • 发现:哪个接口出错最多
  1. 响应时间趋势图
  • 看:接口响应时间变化
  • 发现:什么时候开始变慢
  1. JVM内存堆栈图
  • 看:内存使用情况
  • 发现:是否有内存泄漏
  1. 数据库连接池图
  • 看:连接数变化
  • 发现:连接是否被耗尽

五、告警:让系统自己"喊救命"

监控有了,但总不能24小时盯着屏幕吧?你需要告警------让系统自己"喊救命"。

5.1 配置钉钉告警(最常用)

第一步:创建钉钉机器人

  1. 钉钉群 → 群设置 → 智能群助手 → 添加机器人
  2. 选择"自定义"
  3. 设置机器人名字,比如"系统监控机器人"
  4. 复制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

九、今日思考题

场景:你是公司的技术负责人,需要设计一套健康检查方案:

  1. 给老板看什么?
  • 整体系统是否健康
  • 今天有多少订单
  • 用户增长趋势
  1. 给运维看什么?
  • 服务器CPU、内存
  • 数据库连接数
  • 网络延迟
  1. 给开发看什么?
  • 哪个接口最慢
  • 什么错误最多
  • JVM内存情况
相关推荐
不吃葱的胖虎2 小时前
根据Excel模板,指定单元格坐标填充数据
java·excel
k***92162 小时前
C语言模拟面向对象三大特性与C++实现对比
java·c语言·c++
珑墨2 小时前
【大语言模型】从历史到未来
前端·人工智能·后端·ai·语言模型·自然语言处理·chatgpt
疯狂成瘾者2 小时前
Lombok 可以生成哪些类方法
java·tomcat·maven
Light602 小时前
MyBatis-Plus 全解:从高效 CRUD 到云原生数据层架构的艺术
spring boot·云原生·架构·mybatis·orm·代码生成·数据持久层
于樱花森上飞舞2 小时前
【多线程】CAS和哈希表
java·数据结构·java-ee
七夜zippoe2 小时前
MyBatis插件开发-实现SQL执行耗时监控
java·sql·mybatis·springboot·责任链
水灵龙2 小时前
文件管理自动化:.bat 脚本使用指南
java·服务器·数据库
lbb 小魔仙2 小时前
【Java】Spring Cloud 微服务架构入门:五大核心组件与分布式系统搭建
java·spring cloud·架构