服务部署完成不等于稳定运行。上线后的前30分钟往往是故障高发期,掌握健康检查与日志分析的组合拳,能将平均故障恢复时间(MTTR)从小时级压缩到分钟级。本文围绕6个高频故障场景,给出可直接落地的排查路径。
一、健康检查机制搭建:第一道防线
健康检查不是可选项,而是服务存活的基本探针。没有它,负载均衡会将流量打到已崩溃的实例上。
1.1 Spring Boot Actuator 基础配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| management: endpoints: web: exposure: include: health,info,metrics,prometheus endpoint: health: show-details: always probes: enabled: true health: livenessstate: enabled: true readinessstate: enabled: true
|
1.2 K8s 探针配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 30 periodSeconds: 10 failureThreshold: 3 readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 20 periodSeconds: 5 failureThreshold: 3
|
执行要点:liveness 失败会触发容器重启,readiness 失败只会摘除流量。初始延迟必须大于服务启动时间,否则会陷入重启死循环。
二、启动失败:端口冲突与Bean初始化异常
部署后服务直接无法启动,日志是最快的信息源。
2.1 端口占用
日志特征:
1
| Caused by: java.net.BindException: Address already in use
|
排查命令:
1 2 3 4 5 6
| ss -tlnp | grep 8080
lsof -i :8080
kill -9 <PID>
|
2.2 Bean创建循环依赖
日志特征:
1
| BeanCurrentlyInCreationException: Error creating bean with name 'userService'
|
处理步骤:
- 在日志中搜索
BeanCurrentlyInCreationException,定位Bean名称
- 检查是否新增了带
@PostConstruct 的初始化逻辑
- 使用
@Lazy 打破循环依赖,或重构为事件驱动模式
2.3 配置缺失导致启动失败
1 2
| java -jar app.jar 2>&1 | grep -E "(ERROR|Caused by|BindingFailure)"
|
三、OOM与内存泄漏:从日志到堆转储
OOM不会总是立即崩溃,但日志中会留下明确的死亡轨迹。
3.1 常见OOM类型与日志关键词
| OOM类型 |
日志关键词 |
根因方向 |
| Java heap space |
java.lang.OutOfMemoryError: Java heap space |
大对象/内存泄漏 |
| GC overhead |
GC overhead limit exceeded |
Full GC频繁且回收率低 |
| Metaspace |
OutOfMemoryError: Metaspace |
动态类加载过多 |
| Direct memory |
OutOfMemoryError: Direct buffer memory |
Netty/NIO堆外内存 |
3.2 自动堆转储配置
1 2 3 4
| -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/dumps/heapdump.hprof -XX:OnOutOfMemoryError="sh /data/scripts/alert.sh %p"
|
3.3 在线诊断(不重启服务)
1 2 3 4 5 6 7 8
| jmap -histo:live <PID> | head -30
jmap -dump:format=b,file=/tmp/heap.hprof <PID>
jstat -gcutil <PID> 1000 10
|
可执行建议:使用 Arthas 在线诊断工具,无需重启即可分析:
1 2 3 4 5 6
| java -jar arthas-boot.jar <PID>
heapdump --live /tmp/dump.hprof
trace com.example.ServiceImpl methodName
|
四、数据库连接池耗尽:健康检查的隐藏信号
健康检查返回 DOWN 但服务进程存活,这是典型的资源耗尽场景。
4.1 健康检查暴露的数据库状态
1 2 3 4 5 6 7 8 9 10 11 12 13
| { "status": "DOWN", "components": { "db": { "status": "DOWN", "details": { "database": "MySQL", "validationQuery": "SELECT 1", "error": "HikariPool-1 - Connection is not available" } } } }
|
4.2 日志中的连接池告警
1 2
| WARN HikariPool-1 - Thread starvation or clock leap detected WARN HikariPool-1 - Failed to obtain connection in 30000ms
|
4.3 排查路径
1 2 3 4 5 6 7 8 9
| SHOW PROCESSLIST; SHOW STATUS LIKE 'Threads_connected';
grep -A5 hikari application.yml
grep -C3 "leak" app.log
|
连接池调优参考:
1 2 3 4 5 6
| spring: datasource: hikari: maximum-pool-size: 20 connection-timeout: 3000 leak-detection-threshold: 60000
|
leak-detection-threshold 开启后,连接超过60秒未归还会打印泄漏堆栈,这是定位慢查询和未关连接的利器。
五、依赖服务超时与熔断:链路日志追踪
下游服务超时是级联故障的起点,日志中的时间线是还原现场的关键。
5.1 超时日志特征
1 2
| ERROR FeignClient - Read timed out executing POST http://order-service/api/orders WARN Hystrix - Command timeout, fallback triggered
|
5.2 使用Trace ID串联调用链
1 2 3 4 5 6 7 8 9 10 11 12
| @Component public class TraceIdFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) { String traceId = ((HttpServletRequest) req).getHeader("X-Trace-Id"); if (traceId == null) traceId = UUID.randomUUID().toString().replace("-", ""); MDC.put("traceId", traceId); chain.doFilter(req, res); MDC.clear(); } }
|
日志格式配置:
1
| <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId}] %-5level %logger{36} - %msg%n</pattern>
|
5.3 快速定位超时根因
1 2 3 4 5
| grep "a1b2c3d4" /data/logs/*.log | sort -k1
grep "duration" app.log | awk -F'duration=' '{print $2}' | sort -n | tail -20
|
熔断配置建议:
1 2 3 4 5 6 7 8 9
| resilience4j: circuitbreaker: instances: orderService: failure-rate-threshold: 50 slow-call-rate-threshold: 60 slow-call-duration-threshold: 2s wait-duration-in-open-state: 30s sliding-window-size: 20
|
六、日志治理与告警自动化
散落的日志毫无价值,结构化 + 告警才是运维闭环。
6.1 结构化日志输出
1 2 3 4 5 6
| <appender name="JSON" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="net.logstash.logback.encoder.LogstashEncoder"> <customFields>{"app":"order-service","env":"prod"}</customFields> </encoder> </appender>
|
输出示例:
1
| {"@timestamp":"2024-01-15T10:30:00.123Z","level":"ERROR","logger":"c.e.OrderService","message":"order creation failed","traceId":"a1b2c3d4","app":"order-service"}
|
6.2 关键告警规则
| 告警项 |
日志关键词 |
触发条件 |
告警级别 |
| 服务不可用 |
Started.*failed |
1次/5分钟 |
P0 |
| OOM预兆 |
Full GC |
>5次/分钟 |
P1 |
| 连接池耗尽 |
Connection is not available |
>3次/分钟 |
P1 |
| 下游超时 |
Read timed out |
>10次/分钟 |
P2 |
| 熔断触发 |
circuit breaker opened |
任意1次 |
P1 |
6.3 ELK + Alertmanager 联动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| curl -X GET "localhost:9200/app-logs-*/_search" -H 'Content-Type: application/json' -d' { "query": { "bool": { "must": [ {"match": {"level": "ERROR"}}, {"range": {"@timestamp": {"gte": "now-5m"}}} ] } }, "aggs": { "by_app": {"terms": {"field": "app.keyword"}} } } '
|
落地检查清单:
健康检查告诉你”是否坏了”,日志告诉你”哪里坏了”。两者配合使用,配合结构化的日志输出和自动化告警,才能在故障发生的黄金5分钟内完成定位和恢复。