系统上线那一刻,可观测性就从”加分项”变成”生命线”。第 19 篇把应用搬进了 Kubernetes,这一篇回答集群里的灵魂三问:它没事吧?(健康)它在干什么?(日志与链路)它干得好吗?(指标)。JHipster 在这三块都有生成或可选的基建,我们要做的是把它们接成一条从告警到根因的通路——半夜被告警叫醒时,五分钟内定位问题靠的就是这条路。
三大支柱总览
| 支柱 | 回答的问题 | JHipster 落地 | 存留周期 |
|---|---|---|---|
| 日志 Logging | 当时发生了什么 | Logback(logback-spring.xml) | 天-周 |
| 指标 Metrics | 现在的状态与趋势 | Micrometer + Actuator + Prometheus 选项 | 周-月 |
| 链路 Tracing | 请求经过了谁、卡在哪 | micrometer-tracing + Zipkin 选项 | 天 |
三者由一个关键词串起来:traceId。日志带 traceId 才能与链路互查,指标按 trace 维度聚合才有上下文。这是接入顺序的依据:先日志、再指标、后链路,投入产出比递减但缺一不可。
日志:结构化与 profile 差异
JHipster 的 logback-spring.xml 按 springProfile 分流,这是生成器内建的约定:
<springProfile name="dev">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
<logger name="com.mycompany.app" level="DEBUG"/>
</springProfile>
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="JSON_FILE"/> <!-- 关键差异:结构化 JSON 落盘 -->
</root>
<logger name="com.mycompany.app" level="INFO"/>
</springProfile>
dev 与 prod 的差异设计:
| 维度 | dev | prod |
|---|---|---|
| 输出 | 控制台人类可读 | JSON 文件(采集友好) |
| 业务包级别 | DEBUG | INFO |
| 根级别 | INFO | INFO(动 SQL 日志走 admin 页面运行时调,见下) |
| 目的 | 开发体验 | 机器采集与检索 |
prod 的 JSON 输出靠 logstash-logback-encoder:
<appender name="JSON_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE}.json</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.json</fileNamePattern>
<maxHistory>14</maxHistory>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<customFields>{"service":"myapp"}</customFields>
</encoder>
</appender>
JSON 结构化是检索的前提:ELK/Loki 按 level、logger、traceId 字段过滤,比 grep 文本日志快一个量级。团队日志纪律:日志要带上下文(log.info("订单 {} 已发货", order.getId()) 而不是 log.info("发货成功"))、异常必须整个抛给 logger(log.error("处理失败", ex),不要 ex.getMessage() 手动拼)、ERROR 只留给”需要人介入”的事——把 WARN 当 ERROR 用会让告警疲劳杀死值班同学。
指标:Micrometer + Prometheus + Grafana
Micrometer 是 Spring Boot 的指标门面,actuator 暴露 /management/metrics(人类可读)。选 Prometheus 选项(或自行加 micrometer-registry-prometheus)后增加抓取端点:
management:
endpoints:
web:
base-path: /management
exposure:
include: health,info,metrics,prometheus
metrics:
distribution:
percentiles-histogram:
http.server.requests: true // 直方图,才能算 p95/p99
# 验证抓取端点
curl -s localhost:8080/management/prometheus | grep http_server_requests_seconds
src/main/docker/prometheus.yml 与 grafana.yml 把本地全家桶配好:Prometheus 抓应用、Grafana 出图。K8s 上用 ServiceMonitor/PodMonitor 接集群的 Prometheus Operator(第 19 篇的 Service 标签记得对上)。
必须进大盘的六类指标(我们 Grafana 第一屏的行布局):
- HTTP:
http_server_requests_seconds_count/max按 uri 分组——QPS、错误率、p95 延迟; - JVM:堆使用、GC 暂停时间(
jvm_gc_pause_seconds)——容器内存压力的证据; - 连接池:HikariCP 的活跃/等待连接数——“数据库慢还是池不够”一图定案;
- 业务指标:自定义 Counter/Gauge(下单量、支付失败数)——技术指标正常业务异常时靠它;
- Kafka 消费滞后:消费组 lag——DLT 事故(第 12 篇)的前置预警;
- 运行时间/实例数:重启与扩缩容的时间线,读其他图时的标尺。
自定义业务指标只要两行,别浪费这个能力:
@Component
public class OrderMetrics {
private final Counter createdCounter;
private final Timer processTimer;
public OrderMetrics(MeterRegistry registry) {
this.createdCounter = Counter.builder("business_orders_created_total")
.description("创建订单数").register(registry);
this.processTimer = Timer.builder("business_orders_process")
.description("订单处理耗时").register(registry);
}
}
链路:micrometer-tracing + Zipkin
问答里选 Zipkin(或 OpenTelemetry 导出),生成器引入 micrometer-tracing 与 Brave/OTel bridge,HTTP 调用、@Async、Kafka 消息自动埋点,traceId 自动写入 MDC——日志与链路由此打通:
management:
tracing:
sampling:
probability: 0.1 // 生产 10% 采样,排障时段临时调 1.0
zipkin:
tracing:
endpoint: ${ZIPKIN_ENDPOINT:http://localhost:9411/api/v2/spans}
价值场景:用户反馈”下单偶尔要 8 秒”。日志按 traceId 聚合(Loki 里 {traceId="..."} | json),Zipkin 打开瀑布图,一眼看到 8 秒里 6.5 秒耗在调第三方物流 API——结论从”猜”变成”看”。采样率的取舍:全采样存储成本高,10% 足以发现规律性问题;小流量场景与排障时临时全开。
微服务架构下链路从”有用”变”刚需”:gateway → order-service → DB → Kafka → inventory-service 一条 span 链就是调用图,服务拓扑都不用画。
健康检查:actuator 与 K8s 探针
management:
endpoint:
health:
probes:
enabled: true // 暴露 liveness/readiness 子路径
show-details: never // 对外不泄露组件细节
第 19 篇的探针消费的就是 /management/health/liveness|readiness。补充两条:
- admin 页面(第 13 篇)的 Health 页是给人看的版本,排障时不用 curl;
/management/health整体绿不等于没有隐患,组件级状态(db、mail、diskSpace)要进监控——我们给management_health_standard_components相关状态配了告警,磁盘剩余空间低于 15% 时提前三天就能看到。
慢 SQL 与 Hibernate 统计
数据访问层的观测两板斧:
Hibernate 统计指标(Micrometer 集成开启后自动注册):
spring:
jpa:
properties:
hibernate:
generate_statistics: true // 代价约 3-5%,只在排障窗口开
看 hibernate_query_executions_per_second(查询速率)、hibernate_query_execution_max_seconds(最慢单查询)与 session 打开数。发现查询速率是业务量的几十倍,基本就是 N+1 的实锤——生成代码的关联列表页是重灾区,@EntityGraph 或 fetch join 修掉。
慢 SQL 定位:日志层开 SQL 计时是最轻量的方案(p6spy 或 datasource-proxy 埋点统计),配合 tracing 里 JDBC span 的耗时排序,慢语句十有八九能在”缺索引的 WHERE”和”过大的 LIMIT”里找到。生产上我们维持一条纪律:任何超过 500ms 的 SQL 必须有对应索引或改写方案,在 code review 阶段用测试环境的慢日志拦截,而不是等生产爆发。
告警与 SLI/SLO
告警是可观测性的”最后一公里”,原则只有三条:
- 面向症状而非原因告警:用户可见的错误率/延迟(SLI)才叫醒人,CPU 高这种原因指标让它待在仪表盘里;
- 每条告警都有 runbook,链接直接放告警正文——半夜被叫醒的人不该靠记忆排障;
- 分级:page(立即处理,错误率 >1% 持续 5 分钟)、ticket(当天处理,DLT 积压、磁盘水位)。
SLI/SLO 一句话版本:SLO 是给用户的承诺(如”99.9% 的请求 p95 < 800ms”),SLI 是衡量承诺的指标(成功请求占比、延迟分布),error budget 是两者之差——烧完预算就冻结功能发布还技术债。这一句话加上 Grafana 第一屏,就是团队的 SRE 实践起步配置。
小结
- 通路思维:健康(探针/health)→ 指标(Micrometer 六类大盘)→ 日志(结构化 JSON)→ 链路(traceId 串联),排障顺序与之相反;
- 日志纪律三板斧:带上下文、异常整体进 logger、ERROR 只留给需要人的事;
- traceId 是三大支柱的连接词,10% 采样 + 排障全开;
- Hibernate 统计 + JDBC span 定位 N+1 与慢 SQL,500ms 阈值进 code review;
- 告警面向症状、附带 runbook,SLO 一句话:承诺、指标、预算三件套。
系列导航
- 上一篇:容器化与 Kubernetes
- 下一篇:配置与 Profile 管理
- 相关阅读:缓存策略与性能 · 异步与消息——Kafka 集成 · 性能调优