0%

Java服务部署后:通过健康检查与日志快速定位常见故障

服务部署完成不等于稳定运行。上线后的前30分钟往往是故障高发期,掌握健康检查与日志分析的组合拳,能将平均故障恢复时间(MTTR)从小时级压缩到分钟级。本文围绕6个高频故障场景,给出可直接落地的排查路径。

一、健康检查机制搭建:第一道防线

健康检查不是可选项,而是服务存活的基本探针。没有它,负载均衡会将流量打到已崩溃的实例上。

1.1 Spring Boot Actuator 基础配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# application.yml
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'

处理步骤

  1. 在日志中搜索 BeanCurrentlyInCreationException,定位Bean名称
  2. 检查是否新增了带 @PostConstruct 的初始化逻辑
  3. 使用 @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
# JVM启动参数
-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>

# 查看GC情况
jstat -gcutil <PID> 1000 10

可执行建议:使用 Arthas 在线诊断工具,无需重启即可分析:

1
2
3
4
5
6
# 启动Arthas
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
# 1. 查看数据库当前连接数
SHOW PROCESSLIST;
SHOW STATUS LIKE 'Threads_connected';

# 2. 检查连接池配置
grep -A5 hikari application.yml

# 3. 查找未关闭的连接(日志中的泄漏线索)
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
// MDC注入TraceId
@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
# 按TraceId串联全链路日志
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
<!-- logback-spring.xml -->
<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
# Elasticsearch查询:最近5分钟ERROR日志按服务聚合
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"}}
}
}
'

落地检查清单

  • 健康检查端点已暴露且K8s探针已配置
  • OOM自动堆转储参数已添加
  • 日志包含TraceId且全链路贯通
  • ERROR级别日志已接入告警
  • 连接池泄漏检测已开启
  • 熔断器慢调用阈值已配置

健康检查告诉你”是否坏了”,日志告诉你”哪里坏了”。两者配合使用,配合结构化的日志输出和自动化告警,才能在故障发生的黄金5分钟内完成定位和恢复。

感谢您的支持,您的打赏是我持续创作的动力!