ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring Boot健康检查与监控实战:从Actuator到Prometheus+Grafana

Spring Boot健康检查与监控实战:从Actuator到Prometheus+Grafana 1. 为什么要做健康检查与监控先把监什么和控什么理清楚在Spring Boot项目上线之前很多团队对健康检查的理解就是服务能启动就行对监控的理解就是看一眼堆内存没爆就行。但真正到了生产环境你会发现这两个词背后牵扯的东西远比想象中多。先说一个我踩过的真实场景有一次凌晨两点线上一个订单服务进程还在跑日志也不打报错但接口全部超时调用方那边显示大量5xx。当时负载均衡器配的探活方式是TCP端口探测——进程活着端口也监听着于是流量还在源源不断打进来。结果就是用户那边体验已经崩了我们的监控大屏上却一片绿。这就是典型的只做了存活检查没做就绪检查。健康检查和监控本质上解决的是三件事服务进程在不在Liveness、服务能不能处理请求Readiness、服务各项资源指标健不健康Metrics。很多人只做了第一件后两件完全没做。这篇文章就是把这三件事怎么落地、用什么工具、踩过什么坑一次性讲清楚。Spring Boot生态里健康检查最核心的组件是Actuator监控指标采集最常用的是Micrometer配合Prometheus做存储再用Grafana做可视化一套从检查到展示的闭环就齐了。这套组合拳也是目前市面上绝大多数Java微服务项目的标配。适合刚接手Spring Boot项目、准备给系统补监控能力以及被线上假死问题折磨过的开发者参考文章内容不挑框架版本2.x和3.x都适用。1.1 先搞清楚假死才是健康检查要解决的头号问题假死是JAVA服务运维里最头疼的现象之一。进程没有退出端口还在监听线程池却全部耗尽或者数据库连接池被占满任何请求进来都只能排队等待此时TCP探测依然显示端口通服务继续被路由到流量情况只会越来越糟。Spring Boot把这个问题拆成了两个维度liveness存活和readiness就绪。存活探针回答我还在不在运行就绪探针回答我能不能接收新请求。生产环境里两者缺一不可。你可以在Actuator中通过配置把这两个探针暴露出来management: endpoint: health: probes: enabled: true endpoints: web: exposure: include: health配置完成后/actuator/health/liveness和/actuator/health/readiness这两个端点就生效了。Kubernetes里可以直接拿它们当探针传统部署模式下负载均衡器的健康检查URL也可以指向readiness端点。注意这里有个细节liveness返回DOWN时大概率是进程级别的严重问题比如磁盘只读、堆内存溢出而readiness返回DOWN往往只是暂时的过载或依赖故障这时候应该摘流量而非重启进程。用个生活化的类比liveness是人有没有心跳readiness是人能不能站起来工作。心跳有但不代表能干活。很多生产事故的根源就在于把这两件事混为一谈了。1.2 监控的三个层次状态、指标、链路健康检查只是监控体系的第一层。再往上走是指标监控——JVM内存、GC频率、线程状态、HTTP接口响应时间、QPS、错误率再往上是链路追踪——一次请求经过了哪些服务、每个环节耗时多少。这篇文章主要讲前两层链路追踪是另一个大话题这里不展开。这套体系的搭建逻辑是这样的第一层用Actuator暴露健康状态和基本信息Answer活没活、能不能用。第二层用Micrometer把JVM、线程池、HTTP等指标标准化成Prometheus格式通过/actuator/prometheus端点暴露出去Prometheus定时抓取存储。第三层Grafana消费Prometheus数据把指标画成面板配出告警阈值。实践当中这三层不一定非得分三个阶段做。很多人一开始只加了个Actuator发现只能看零散的JSON数据不舒服后来接了个Prometheus终于有了指标趋势图再后来才被Grafana的可视化和告警圈粉——这个过程几乎是每个团队都会走的路径。成熟的团队通常会直接一次到位但不影响你从理解的角度去渐进式搭建。2. Actuator的健康检查从默认状态到业务自定义状态2.1 引入依赖、暴露端点一次到位Actuator的引入方式很简单Maven项目加一个依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-actuator/artifactId /dependencyGradle对应的写法是implementation org.springframework.boot:spring-boot-starter-actuator。加完依赖重启应用访问/actuator就能看到一组JSON端点列表。但这里有个关键的坑Spring Boot 2.x之后默认只暴露了health和info两个端点其他端点即使加了依赖也不开放需要在配置里显式声明Spring Boot 3.x同样延续了这个安全策略。如果你在本地调试想临时把常用端点全开可以这么配management: endpoints: web: exposure: include: health,info,metrics,env,loggers,threaddump,heapdump但生产环境我强烈建议别全量开放尤其是env、heapdump、shutdown这几个端点能不开就不开。env会泄露环境变量和配置项heapdump会直接导出堆内存快照文件都是敏感操作。如果你用的是Spring Boot 3.x还要多注意一个点management.endpoints.web.exposure.include的默认行为没变但端点路径、属性名有些微调升级前翻一下官方迁移文档。这里给个我实际用的生产配置模板management: endpoints: web: exposure: include: health,info,metrics,prometheus,loggers,threaddump endpoint: health: show-details: when-authorized probes: enabled: true loggers: enabled: true server: port: 8081注意最后那个management.server.port把管理端点单独放到一个端口上和业务端口隔离。好处有两个一是监控采集系统比如Prometheus可以只抓管理端口不影响正常业务流量二是可以配合防火墙策略只允许内网访问管理端口减少暴露面。2.2 自定义HealthIndicator把业务依赖状态暴露给探针Actuator自带的健康检查会汇总很多自动配置的健康指示器比如数据库DataSourceHealthIndicator、Redis、RabbitMQ、MongoDB等。但很多业务场景里服务的健康状态还取决于一些外部依赖比如第三方支付接口、对象存储、某个内部RPC服务。这些依赖不在Spring Boot自动配置范围内你需要自己写一个HealthIndicator。自定义的写法在Spring Boot 2.x和3.x里略有差别但核心思想一样实现HealthIndicator接口重写health()方法。2.x示例如下Component public class CustomApiHealthIndicator implements HealthIndicator { private final RestTemplate restTemplate new RestTemplate(); Override public Health health() { try { ResponseEntityString response restTemplate.exchange( https://api.example.com/health, HttpMethod.GET, null, String.class ); if (response.getStatusCode().is2xxSuccessful()) { return Health.up() .withDetail(code, response.getStatusCode().value()) .build(); } return Health.down() .withDetail(code, response.getStatusCode().value()) .build(); } catch (Exception e) { return Health.down(e) .withDetail(reason, e.getMessage()) .build(); } } }在Spring Boot 3.x里HealthIndicator仍然是函数式接口可以直接用lambda表达式Component public class CustomApiHealthIndicator implements HealthIndicator { Override public Health health() { // 一样的逻辑 } }这里想强调一个经验Health.down()要慎用。如果一个不重要的外部依赖挂了强行把整体健康状态置为DOWN可能导致负载均衡器把这个实例摘掉流量全部打到其他实例上引发连锁反应。我的建议是按依赖的重要程度分级——核心依赖失败返回DOWN非核心依赖失败可以返回UP但要带上自定义的status和detail或者使用Health.status(DEGRADED)这种自定义状态码让监控人员能看到这实例其实是半残状态。2.3 端点的安全防护别把管理端口当摆设前面提到管理端口独立出来后安全防护要做的事还远不止换个端口。实际生产经验里至少有四件坑要提前埋好第一件不要裸奔在公网。管理端点上有很多敏感信息比如/actuator/env能看配置/actuator/threaddump能看线程栈/actuator/mappings能看所有URL映射。这些信息对内部排查问题很有用被公网看到就是信息泄露。管理端口至少要限制在内网IP段最好再加一层防火墙规则别只依赖网络边界。第二件如果和Spring Security共存要单独配置权限。业务接口走一遍安全认证没问题但监控采集系统比如Prometheus通常不方便带Cookie或Token去访问端点所以需要给管理端点单独的放行规则。常见的做法是加一个SecurityFilterChain来专门处理/actuator/**路径同时设置IP白名单Header比如X-Forwarded-For校验既让采集系统能访问又避免公网随便调。第三件管理端口不要暴露到注册中心。如果服务注册到Nacos或Eureka默认注册的是主端口。Spring Boot提供了management.server.add-application-context-header等配置项但更稳妥的办法是让服务把自己的IP和主端口注册到注册中心管理端口只在内部固定端口访问不参与服务注册发现。否则外部服务可能通过注册中心拿到管理端口绕过业务安全逻辑直接访问内部调试信息。第四件用shutdown端点时三思。很多人在本地调试喜欢打开shutdown端点图方便生产环境千万别开。开了就意味着任何能访问管理端口的人都能优雅关停你的服务等于是给攻击者递了一把刀。真需要远程重启服务的话走操作系统的systemd或容器平台去做而不是暴露Spring的shutdown端点。3. 指标采集进阶Micrometer配合Prometheus与Grafana3.1 引入Micrometer注册表一分钟暴露Prometheus格式指标Actuator自带的/actuator/metrics端点能查看内存、线程等JVM指标但它的本职工作是展示而不是存储历史趋势、聚合分析这些就别指望它了。想要真正做监控得用Prometheus来抓取指标而Micrometer就是两者之间的桥。在Spring Boot 2.x中加一个依赖dependency groupIdio.micrometer/groupId artifactIdmicrometer-registry-prometheus/artifactId /dependency如果是Spring Boot 3.x对应的坐标是io.micrometer:micrometer-registry-prometheus:1.11.x版本跟随Boot父工程管理通常不用显式写版本号。加完依赖后上面配置的management.endpoints.web.exposure.include里加上prometheus重启应用访问/actuator/prometheus你就能看到一堆Prometheus格式的指标文本。这个端点输出的就是我们上面配置模板里留了prometheus的原因。有了这个端点Prometheus服务器的抓取配置就很简单只需在prometheus.yml里加一个jobscrape_configs: - job_name: spring-boot-app metrics_path: /actuator/prometheus static_configs: - targets: [192.168.1.10:8081]这里注意一点Prometheus抓取的是管理端口8081而不是业务端口8080这样业务流量再大也不会影响指标的抓取。抓取间隔建议默认的15s就够用不要设置太频繁否则对高并发服务反而是额外的开销。3.2 核心指标解读JVM、线程、HTTP、数据库连接池暴露了Prometheus指标之后面对/actuator/prometheus那几百行文本很多新手会懵。我给你挑几个必须盯住的硬指标指标名含义告警参考阈值jvm_memory_used_bytesJVM堆和非堆内存使用量堆使用率持续超过85%要警惕jvm_gc_pause_secondsGC暂停时间平均值超过100ms需要优化system_cpu_usage系统CPU使用率长期超过80%要扩容http_server_requests_secondsHTTP请求耗时分布p99超过200ms要关注tomcat_threads_busyTomcat忙线程数接近最大值时说明线程池不够hikaricp_connections_activeHikariCP活跃连接数接近最大连接数时要看慢SQLprocess_uptime_seconds进程运行时长异常重启会发现数值突然归零以http_server_requests_seconds为例这个指标是Micrometer对HTTP请求的自动埋点默认会按请求URI和方法打标签。查询语句可以这样写histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket[5m])) by (le, uri))这条语句算的是5分钟内所有接口的p99耗时。注意一个坑如果按URI分组有些URI里带了动态参数比如/order/123和/order/456会被分别计数导致指标基数膨胀。生产环境建议按URI模板做归一化或者只统计特定几个核心接口否则Prometheus的存储压力会很大。tomcat_threads_busy这个指标也值得多说一句。通过tomcat.threads.busy也就是tomcat_threads_busy{namehttp-nio-8080}你可以清楚地看到当前Tomcat线程池的忙碌程度。如果这个值长时间接近tomcat_threads_max但CPU并不高很可能是有外部阻塞调用比如慢HTTP调用、数据库连接等待卡住了线程这时候看dump会有大收获。数据库连接池的指标通过HikariCP自动上报。hikaricp_connections_active接近hikaricp_connections_max时配合数据库慢查询日志基本就能定位到问题了。我见过不少项目把最大连接数设到200甚至500其实很多场景下20~50就够了连接池过大反而会增加数据库侧的开销。3.3 Grafana看板不用从零画导入现成模板再改细节Grafana看板从来不需要从零开始画。社区里有大量现成模板可以直接导入我推荐两个最经典的方向JVM监控看板比如Grafana社区的4701号看板基于Micrometer的JVM指标覆盖堆内存、GC、线程、类加载等导入后改一下数据源即可。Spring Boot综合看板社区里搜Spring Boot有很多带HTTP请求统计和连接池指标的看板模板比如12900适用于Micrometer的Spring Boot 2.x/3.x。导入模板之后别急着用先做几件事确认数据源指向Prometheus把面板里的label名称改成实际环境里的比如有些模板用的是application标签而你的是job调整时间范围和刷新间隔。我经常见到有人导入了一个漂亮的看板结果因为label不匹配所有查询都是No Data浪费了半小时排查配置问题。自定义看板的话有几个OpenMetrics标签要注意application默认取spring.application.name如果没设置很多聚合查询会失效。建议每个服务在bootstrap.yml或application.yml里都设置spring: application: name: order-service这个application标签在Grafana上可以做多服务聚合对比。比如一张看板里同时看order-service、user-service、payment-service的JVM堆使用率直接按application分组查询几行配置就能搞定。4. 监控不告警等于白监控Prometheus告警规则与通知链4.1 核心告警规则先保证最要紧的几件事Grafana面板再好看人不可能24小时盯着屏幕。监控的最后一环是告警而告警首先要解决的问题是别乱叫。我给生产环境配置告警时遵循一个原则只告警那些真正需要人工介入的事。举个例子Uptime指标如果长期正常就不用为它配告警但实例挂了这种绝对的大事必须有。Prometheus里通过up指标来判断实例是否在抓取周期内响应配合for子句可以过滤掉瞬时抖动groups: - name: spring-boot-alerts rules: - alert: InstanceDown expr: up 0 for: 2m labels: severity: critical annotations: summary: 实例 {{ $labels.instance }} 已离线超过2分钟 - alert: HighJvmMemoryUsage expr: jvm_memory_used_bytes{areaheap} / jvm_memory_max_bytes{areaheap} 0.85 for: 10m labels: severity: warning annotations: summary: JVM堆内存使用率超过85%持续10分钟 - alert: HighErrorRate expr: sum(rate(http_server_requests_seconds_count{status~5..}[5m])) by (job) / sum(rate(http_server_requests_seconds_count[5m])) by (job) 0.05 for: 5m labels: severity: warning annotations: summary: 5xx错误率超过5%持续5分钟这里有个经验想分享报警阈值宁缺毋滥。一开始配置告警很容易陷入所有指标都配一条规则的误区结果就是告警风暴值班同事一天收几十条告警邮件最后全都忽略真正出大事反而没人看。我的建议是先只配三个最核心的告警实例挂了、堆内存超阈值、5xx错误率飙升。跑一阵子确认告警通知通道稳定了再逐步增加GC相关、线程池耗尽等更细粒度的规则。4.2 告警通知链从AlertManager到钉钉、企业微信或邮件Prometheus本身只负责存储和规则评估真正把告警推给人的是AlertManager。AlertManager的安装配置不展开细说核心思路是定义一条路由把所有severitycritical的告警发到紧急通道非紧急的发到常规群。这里给一个最小可用的AlertManager配置route: group_by: [alertname] group_wait: 10s group_interval: 5m repeat_interval: 4h receiver: default-receiver receivers: - name: default-receiver webhook_configs: - url: http://localhost:5000/send send_resolved: truewebhook_configs是AlertManager最灵活的出口你可以自己写一个极简Webhook服务接收告警JSON再转成你们内部IM的消息格式。钉钉、企业微信的机器人文档里都有自定义Webhook的接入方式照着拼一个JSON POST上去就行。个人建议先把send_resolved设为true意思是恢复时也发通知这样值班的人能知道刚才的告警已经恢复了而不是提心吊胆地等一波晋级的告警。告警恢复消息很容易淹没在大量告警里所以强烈建议把恢复和触发分开按严重程度走不同通道。4.3 阈值怎么定才不狼来了聊聊告警调优的经验狼来了是告警系统最大的敌人。如果告警阈值设置太敏感一天响几十次但每次都不是真问题值班的人就会麻掉。如果设置太迟钝真出事了又没反应。调优告警阈值时我会按月为单位复盘看一周内的告警记录统计哪些告警最终确认是有效告警哪些是误报。比如HighJvmMemoryUsage持续告警查下来是开发环境测试代码在不停地分配大对象属于环境问题那这条规则就不该在开发环境启用。再比如HighErrorRate因为某个爬虫在疯狂打接口导致5xx那就应该考虑把爬虫IP加黑名单而不是调高错误率阈值。还有个小技巧给告警规则打上环境标签比如environment生产然后只让生产环境的规则发往告警接收人测试和预发环境的告警全部静默或只发到日志。不然开发环境半夜一个OOM告警照样把运维和开发都喊醒那就得不偿失了。5. 常见问题与排查技巧实录5.1 Actuator端点401或404的排查思路这是一个高频问题。加了Actuator依赖后访问/actuator/health结果404或401。排查路径其实是固定的404先确认management.endpoints.web.exposure.include里有没有包含health。Spring Boot 2.x以后默认只暴露health和info但如果你自定义配置了include就一定要把health也带上。404还能出现在另一个场景项目里自定义了server.servlet.context-path比如/apiActuator端点的路径默认是/api/actuator/health这个要注意。想改成独立路径的话配置management.endpoints.web.base-path/health即可。401说明有Spring Security在拦。解决方案是给/actuator/**单独放行但如果你的管理端口只对内网可见可以直接对/actuator/**关闭认证否则就用IP白名单保证安全的前提下放行。还有一种容易被忽略的404Spring Boot 3.x中如果应用配置了server.forward-headers-strategy部分反向代理场景下/actuator/health转发路径可能被改写排查时建议先直接访问应用本身的IP:端口绕过代理确认端点本身是否可用。5.2 Prometheus指标太多怎么办高基数问题的实战处理Micrometer的标签机制很方便但用不好就是灾难。最常见的坑是给没上限的值打标签比如把userId、orderId这种唯一标识作为Tag加到指标上。每个请求都会产生新的标签组合指标基数无限膨胀Prometheus的内存和磁盘迟早爆掉。实际处理中我会把握两个原则只对有限集合的值打标签比如method、status、uri尽量按URI模板。高基数数据如果要分析用日志系统或者链路追踪来解决而不是硬塞给Prometheus。如果已经发现Prometheus的tsdb_head_samples_appended_total在不停涨先看哪些指标的标签基数最大。promtool可以分析TSDB统计Grafana或者Prometheus的/api/v1/status/tsdb接口能列出Top10高基数标签定位后改代码重建指标。5.3 服务假死与探针配置我把这些坑替你踩过了回到开头说的假死问题。假如你的服务部署在Kubernetes里容器探针配置是这么干的livenessProbe: httpGet: path: /actuator/health/liveness port: 8081 initialDelaySeconds: 60 periodSeconds: 10 readinessProbe: httpGet: path: /actuator/health/readiness port: 8081 initialDelaySeconds: 30 periodSeconds: 5传统虚拟机上用负载均衡的话把健康检查URL指向/actuator/health/readiness就行。但这里有个细微差别要重点提醒如果自动配置的、且你主动自定义的HealthIndicator返回DOWNreadiness端点会整体返回503负载均衡就能及时把流量摘走反之如果你只用TCP端口探测它永远不知道连接池已耗尽。还有一个坑是initialDelaySeconds设置。如果你的服务启动很慢比如依赖外部系统预热initialDelaySeconds设置太短会导致启动期间反复被探活失败容器被不断重启进入崩坏循环。建议初值设大一些按实际启动耗时再加30到60秒的余量。等稳定之后观察一段时间的启动耗时再来精调这个参数。5.4 一个经常被忽略的细节时区与历史数据保留策略Grafana面板上看指标趋势经常发现时间轴对不上——明明现在是下午三点看板显示凌晨三点。这不是指标采集错了而是时区问题。Grafana的默认时区是UTC需要在看板设置里把Timezone改成Local或者指定Asia/Shanghai。Prometheus存储的数据都是UTC时间戳展示层负责转换成当地时区千万锁定看板设置否则不同浏览器进来看的时间可能不一致。数据保留策略也是一个容易被忽略的日常维护项。Prometheus默认保留retention配置的时间序列数据如果设置成永久磁盘会越吃越满。一般生产中建议设置15天左右storage: tsdb: retention: time: 15d对这个配置我要多说一句Prometheus里retention是可以按time和size同时限制的生产环境建议两个都配。如果只配time不配size大量高基数指标可能提前把磁盘写满特别是采集了带高基数标签的指标时。Prometheus官方文档里给出的建议是同时评估这两个维度的上限避免单方面约束导致烦人的存储问题。写在最后的一点实际体会这套健康检查和监控体系我前后在多个项目里搭过踩过的坑比写出来的多。最大的体会是先从最小闭环开始不要一开始就追求全明星配置。加一个Actuator依赖把/actuator/health用起来再挂一个Prometheus抓JVM和HTTP两个维度的指标最后接一个Grafana看板配两到三条核心告警。这个流程一天就能跑通但带来的价值远超预期。等这套最小闭环稳定运行两个星期你会对服务的真实运行状态有非常直观的感受哪些接口的耗时在悄悄变长哪个时间段GC特别频繁线程池的忙碌水位大概在什么水平。有了这些数据做底子再去调整告警阈值、增加自定义健康检查项、优化连接池参数就有了依据而不是凭感觉瞎调。最后再分享一个小技巧平时多练习读PromQL。监控体系搭好的头几天我建议每天写一两个PromQL查询练手比如按URI统计5分钟内平均耗时对比今天和昨天同一时间段的CPU使用率。这些查询练熟了遇到线上问题的时候你能比别人快很多定位到瓶颈。监控的价值不在于面板有多好看而在于出问题的时候你能不能从数据里看出门道。
返回列表