主题
十五、线上故障排查(10 题)
15.1 ★★★★线上服务 CPU 飙升到 100%,你的排查思路和步骤是什么?(结合 top + top -Hp + jstack 分析线程栈)
📖 点击查看答案
排查步骤(经典三件套):
1. 定位进程:top 找出 CPU 最高的 Java 进程
bash
top
# 假设找到 PID = 12345 的 Java 进程 CPU 100%2. 定位线程:top -Hp <pid> 查看进程内线程 CPU 占用
bash
top -Hp 12345
# 找到 CPU 最高的线程 TID = 12378(十进制)3. 线程 ID 转十六进制:jstack 输出的 nid 是十六进制
bash
printf "%x\n" 12378
# 输出 305a4. dump 线程栈并分析:
bash
jstack 12345 > /tmp/thread_dump.txt
grep "nid=0x305a" -A 30 /tmp/thread_dump.txt
# 查看该线程在执行什么代码常见 CPU 飙高原因:
- 死循环 / 无限递归:业务逻辑 bug
- 频繁 Full GC:jstat 查看 GC 情况
- 正则回溯:恶意输入导致正则灾难
- 序列化/反序列化:大对象循环引用
- 加密/哈希计算:如大量密码加密
- 线程上下文切换过多:线程数过多
bash
# 辅助命令
# 查看 GC 情况
jstat -gcutil 12345 1000 5
# 查看线程数
jstack 12345 | grep "java.lang.Thread.State" | wc -l
# 查看死锁
jstack -l 12345 | grep -A 20 "Found .* deadlock"
# 一键排查脚本
#!/bin/bash
pid=$1
tid=$(top -Hp $pid -n 1 | awk 'NR>7 {print $1, $9}' | sort -k2 -rn | head -1 | awk '{print $1}')
hex=$(printf "%x\n" $tid)
jstack $pid | grep "nid=0x$hex" -A 30其他工具:
- Arthas:
thread -n 3直接看 CPU 最高的 3 个线程 - async-profiler:生成火焰图,定位方法级 CPU 消耗
- JProfiler / VisualVM:GUI 工具实时监控
Arthas 一键排查:
bash
# 启动 arthas
java -jar arthas-boot.jar <pid>
# 查看 CPU 占用最高的线程
thread -n 3
# 查看具体方法耗时
trace com.example.OrderService createOrder15.2 ★★★★线上服务频繁 Full GC / OOM 如何快速定位问题?可能的原因有哪些?如何从 GC 日志中定位问题?Heap Dump 的分析思路是什么?
📖 点击查看答案
频繁 Full GC / OOM 常见原因:
- 内存泄漏:静态集合持有对象不释放、ThreadLocal 未 remove
- 大对象:大 SQL 结果集、大文件加载、大 List
- 内存分配过快:高并发创建对象,Young GC 跟不上
- 堆配置不合理:堆太小、年轻代/老年代比例不当
- Metaspace 溢出:动态加载类过多(如反射、Groovy)
- Direct Memory 溢出:NIO ByteBuffer 堆外内存泄漏
- System.gc() 被频繁调用:代码或框架主动触发
排查步骤:
1. 查看 GC 日志:
bash
# JVM 参数建议
-Xms4g -Xmx4g -Xmn2g
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-Xloggc:/path/gc.log
-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M
# 分析 GC 日志
# Full GC 频率、耗时、回收后老年代大小
# 如 Full GC 后老年代没怎么下降 → 内存泄漏2. jstat 实时监控:
bash
jstat -gcutil <pid> 1000 10
# 观察 O(老年代使用率)是否持续增长不回收3. Dump 堆内存分析:
bash
# 手动 dump
jmap -dump:format=b,file=heap.hprof <pid>
# OOM 自动 dump(推荐 JVM 参数)
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/path/dump
# MAT 分析
# 1. 打开 hprof 文件
# 2. 查看 Dominator Tree(支配树)找最大对象
# 3. 查看 Histogram 按类统计对象数量
# 4. Leak Suspects 报告自动定位泄漏点
# 5. 查看对象的 GC Root 引用链4. 常见内存泄漏场景与排查:
java
// 场景1:静态集合泄漏
static Map<String, Object> cache = new HashMap<>();
// 缓存无限增长,未设置淘汰策略 → 用 Caffeine/Guava 设置 maxSize/expire
// 场景2:ThreadLocal 泄漏
ThreadLocal<byte[]> tl = new ThreadLocal<>();
tl.set(new byte[1024 * 1024]);
// 线程池中线程复用,未 remove → finally 中 tl.remove()
// 场景3:监听器未移除
eventBus.register(listener);
// 忘记 unregister → 在 destroy 方法中移除
// 场景4:数据库连接/IO 未关闭
Connection conn = dataSource.getConnection();
// 异常时未关闭 → try-with-resourcesMAT 分析要点:
- Leak Suspects:自动报告最可能的泄漏点
- Dominator Tree:按对象持有内存大小排序
- Histogram:按类查看实例数和内存占用
- Path to GC Roots:查看对象为何无法被回收(引用链)
- OQL 查询:
SELECT * FROM java.lang.String s WHERE s.count > 1000
OOM 类型:
Java heap space:堆内存不足GC overhead limit exceeded:GC 花费超过 98% 时间但回收少于 2%Metaspace:元空间不足Direct buffer memory:堆外内存不足unable to create new native thread:线程数过多
15.3 ★★线上服务出现线程死锁如何排查?jstack 的使用方法是什么?如何分析线程堆栈?
📖 点击查看答案
排查步骤:
1. 确认死锁:jstack 会自动检测死锁并打印
bash
jstack <pid> > /tmp/jstack.txt
grep -A 20 "Found .* deadlock" /tmp/jstack.txt
# 或直接
jstack -l <pid> | grep -A 30 "deadlock"2. 分析死锁信息:
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x00007f8b0c0063e8 (object 0x000000076b6a3e98, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x00007f8b0c003ed8 (object 0x000000076b6a3ea8, a java.lang.Object),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at com.example.Service.methodB(Service.java:20)
- waiting to lock <0x000000076b6a3e98>
- locked <0x000000076b6a3ea8>
"Thread-0":
at com.example.Service.methodA(Service.java:10)
- waiting to lock <0x000000076b6a3ea8>
- locked <0x000000076b6a3e98>3. 关键信息解读:
waiting to lock <0x...>:等待获取的锁locked <0x...>:已持有的锁- 锁对象地址对应,即可确认死锁环路
经典死锁示例:
java
class Service {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void methodA() {
synchronized (lock1) { // 持有 lock1
synchronized (lock2) { // 等待 lock2
// ...
}
}
}
public void methodB() {
synchronized (lock2) { // 持有 lock2
synchronized (lock1) { // 等待 lock1
// ...
}
}
}
}死锁排查工具:
- jstack:命令行,最常用
- Arthas:
thread -b查找阻塞其他线程的线程 - VisualVM / JConsole:可视化查看线程状态和死锁
- JProfiler / MAT:高级分析
Arthas 一键排查:
bash
# 查找阻塞其他线程的线程(死锁根源)
thread -b
# 查看所有线程状态
thread
# 查看特定线程
thread <thread_id>死锁预防:
- 固定锁顺序:所有线程按相同顺序获取锁
- 尝试获取锁带超时:
tryLock(timeout)避免永久阻塞 - 减少锁粒度:使用细粒度锁
- 无锁编程:CAS、ConcurrentHashMap、队列
- 避免嵌套锁:尽量减少同步块嵌套
java
// 预防方案1:tryLock 超时
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
try {
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
try { /* ... */ } finally { lock2.unlock(); }
}
} finally { lock1.unlock(); }
}
// 预防方案2:按 hash 排序加锁
void transfer(Account from, Account to) {
Account first = from.hashCode() < to.hashCode() ? from : to;
Account second = from.hashCode() < to.hashCode() ? to : from;
synchronized (first) {
synchronized (second) { /* ... */ }
}
}15.4 ★★★某接口响应突然变慢(从 50ms 到 5s),如何排查?从哪些层面入手分析?
📖 点击查看答案
分层排查思路:
1. 应用层:
- 是否有慢方法/循环(APM 链路追踪:SkyWalking/Pinpoint)
- 是否 Full GC 导致 STW(jstat 查看)
- 线程池是否打满(jstack 查看 BLOCKED 线程)
- 锁竞争是否激烈
2. 数据库层:
- 是否有慢 SQL(慢查询日志)
- 是否索引失效(EXPLAIN 分析执行计划)
- 是否锁等待(
SHOW PROCESSLIST、innodb_lock_waits) - 连接池是否耗尽
3. 中间件层:
- Redis 是否慢查询(
SLOWLOG GET)、大 key、热点 key - MQ 是否消费堆积、消费速度跟不上
- 网络是否抖动(ping/telnet)
4. 系统层:
- CPU/内存/磁盘 IO/网络是否异常(top/iostat/vmstat)
- 是否被其他进程影响
5. 外部依赖:
- 下游服务是否变慢(RPC 超时配置)
- 第三方 API 是否限流/超时
排查工具链:
bash
# 1. 查看接口监控(APM)
# SkyWalking 链路追踪,定位耗时在哪个环节
# 2. 查看应用状态
jstat -gcutil <pid> 1000 5 # GC 情况
jstack <pid> | grep -A 5 "BLOCKED" # 阻塞线程
# 3. 数据库排查
SHOW PROCESSLIST; # 当前执行的 SQL
SHOW ENGINE INNODB STATUS; # 锁等待
# 慢查询日志
pt-query-digest /var/log/mysql/slow.log
# 4. Redis 排查
redis-cli SLOWLOG GET 10 # 慢查询
redis-cli --bigkeys # 大 key
redis-cli INFO clients # 连接数
# 5. 系统资源
top # CPU
iostat -x 1 # 磁盘 IO
vmstat 1 # 系统状态常见原因与解决:
| 原因 | 现象 | 解决方案 |
|---|---|---|
| 慢 SQL | 数据库耗时高 | 加索引、优化 SQL、分页 |
| Full GC | 间歇性卡顿 | 调整 JVM 参数、排查内存泄漏 |
| 锁等待 | 线程 BLOCKED | 优化锁粒度、避免长事务 |
| 连接池耗尽 | 获取连接超时 | 增大连接池、优化连接释放 |
| Redis 大 key | Redis 耗时高 | 拆分大 key、pipeline |
| 网络抖动 | RPC 超时 | 重试、超时降级 |
| 下游变慢 | 链路耗时增加 | 熔断降级、异步化 |
Arthas 逐层定位:
bash
# 监控方法耗时
trace com.example.OrderService createOrder -n 5
# 查看方法入参出参
watch com.example.OrderService createOrder "{params, returnObj}" -x 2
# 反编译确认线上代码
jad com.example.OrderService15.5 ★★如何排查 MySQL 慢查询?慢查询日志如何分析?pt-query-digest / EXPLAIN 怎么使用?数据库 CPU 飙高如何定位到具体 SQL?
📖 点击查看答案
1. 开启慢查询日志:
sql
-- 动态开启
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1; -- 超过 1s 记录
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
SET GLOBAL log_queries_not_using_indexes = ON; -- 记录未用索引的查询
-- 查看配置
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';2. pt-query-digest 分析慢日志:
bash
# 基本用法
pt-query-digest /var/log/mysql/slow.log > slow_report.txt
# 输出包含:
# - 统计概览:总查询数、唯一查询数、总耗时
# - 按耗时排序的 TOP 查询
# - 每个查询的执行次数、平均耗时、返回行数
# - 示例 SQL
# 实时分析
tail -f /var/log/mysql/slow.log | pt-query-digest --
# 按数据库过滤
pt-query-digest --filter '$event->{db} =~ /order_db/' slow.log3. EXPLAIN 分析执行计划:
sql
EXPLAIN SELECT * FROM t_order WHERE user_id = 1001;
-- 关注字段:
-- type:访问类型(system > const > eq_ref > ref > range > index > ALL)
-- ALL 全表扫描需优化,ref/range 较好
-- key:实际使用的索引
-- rows:预估扫描行数(越少越好)
-- Extra:
-- Using index:覆盖索引,好
-- Using filesort:额外排序,需优化
-- Using temporary:临时表,需优化
-- Using where:使用 WHERE 过滤
-- 查看详细成本
EXPLAIN FORMAT=JSON SELECT ...;
-- 或
EXPLAIN ANALYZE SELECT ...; -- MySQL 8.0+4. 数据库 CPU 飙高定位 SQL:
sql
-- 查看当前执行的 SQL
SHOW PROCESSLIST;
SHOW FULL PROCESSLIST;
-- 查看耗时最长的查询
SELECT * FROM information_schema.processlist
WHERE time > 10 ORDER BY time DESC;
-- 查看正在执行的 SQL 详情
SELECT * FROM performance_schema.events_statements_current;
-- InnoDB 引擎状态(锁等待、死锁)
SHOW ENGINE INNODB STATUS\G
-- 查看锁等待
SELECT * FROM information_schema.innodb_lock_waits;
SELECT * FROM information_schema.innodb_trx;
-- 性能分析(MySQL 5.7+ performance_schema)
-- 按耗时 TOP SQL
SELECT digest_text, count_star, avg_timer_wait/1000000000 avg_ms
FROM performance_schema.events_statements_summary_by_digest
ORDER BY avg_timer_wait DESC LIMIT 10;5. 常见慢查询原因与优化:
sql
-- 原因1:未走索引
-- 优化:添加合适索引
ALTER TABLE t_order ADD INDEX idx_user_id (user_id);
-- 原因2:索引失效
-- 函数操作:WHERE DATE(create_time) = '2024-01-01' → 改为范围查询
-- 隐式转换:WHERE varchar_col = 123 → WHERE varchar_col = '123'
-- 前导模糊:WHERE name LIKE '%abc' → 全文索引或后缀模糊
-- OR 连接:WHERE a=1 OR b=2(b无索引)→ UNION ALL 或都加索引
-- 原因3:返回数据量大
SELECT * FROM t_order WHERE ... → SELECT id, name FROM ...
-- 分页:LIMIT 0, 10 而非全量
-- 原因4:JOIN 关联过多
-- 小表驱动大表,被驱动表的关联字段加索引
-- 原因5:深分页
SELECT * FROM t_order LIMIT 1000000, 10
-- 优化:游标分页
SELECT * FROM t_order WHERE id > 1000000 LIMIT 1015.6 ★线上 Redis 内存使用率接近 maxmemory,如何排查和解决?
📖 点击查看答案
排查步骤:
1. 查看内存概况:
bash
redis-cli INFO memory
# 关注:
# used_memory:已用内存
# used_memory_rss:操作系统分配的内存
# maxmemory:最大内存限制
# mem_fragmentation_ratio:内存碎片率(>1.5 需关注)
# evicted_keys:因内存淘汰的 key 数量
redis-cli INFO stats | grep evicted # 查看是否在淘汰 key2. 分析 key 分布:
bash
# 大 key 扫描
redis-cli --bigkeys
# 或
redis-cli --memkeys # Redis 6.0+ 按内存分析
# 扫描指定前缀的 key 数量
redis-cli --scan --pattern 'session:*' | wc -l
# 查看单个 key 占用内存
MEMORY USAGE <key> SAMPLES 10003. 定位大 key:
bash
# 离线分析 RDB 文件
rdb-tools --command memory dump.rdb > memory.csv
# 或
redis-rdb-tools dump.rdb -f memory.csv
# 实时分析(redis 4.0+)
MEMORY STATS常见原因与解决方案:
| 原因 | 解决方案 |
|---|---|
| 大 key(如大 List/Hash) | 拆分为多个小 key,或迁移到 DB |
| 未设置过期时间的 key 堆积 | 设置 TTL,定期清理 |
| 缓存无淘汰策略或策略不当 | 配置 maxmemory-policy allkeys-lru |
| 热点 key 占用大 | 分片存储、压缩 value |
| 内存碎片过多 | 重启 Redis 或 MEMORY PURGE(jemalloc) |
解决方案:
bash
# 1. 设置淘汰策略
CONFIG SET maxmemory-policy allkeys-lru
# 可选策略:
# noeviction:不淘汰(默认,OOM 时拒绝写入)
# allkeys-lru:所有 key 中 LRU 淘汰
# volatile-lru:设了 TTL 的 key 中 LRU 淘汰
# allkeys-random:随机淘汰
# volatile-ttl:淘汰即将过期的
# 2. 批量删除大 key(避免阻塞)
redis-cli --scan --pattern 'temp:*' | xargs redis-cli DEL
# Redis 4.0+ 异步删除
UNLINK key1 key2 key3
# 3. 设置 TTL
EXPIRE key 3600
# 4. 大 key 拆分
# 原:HSET user:1001 {name, age, addr, history(大)...}
# 拆:HSET user:1001:basic {name, age}
# HSET user:1001:addr {addr}
# LIST user:1001:history ...预防措施:
- 单 key 大小不超过 10KB(Hash/List 不超过 5000 元素)
- 所有缓存必须设置 TTL
- 监控内存使用率(告警阈值 80%)
- 定期巡检大 key
- 使用 Redis Cluster 分散压力
15.7 ★★消息队列消息堆积、消费失败如何处理?MQ 消息大量堆积来不及消费,如何应急处理?
📖 点击查看答案
消息堆积排查:
- 查看消费者状态:是否存活、是否在消费
- 查看消费速度(TPS)vs 生产速度
- 查看是否有消费失败导致重试堆积
- 查看消费者线程数是否足够
- 查看下游依赖(DB/Redis)是否变慢
应急处理方案:
1. 临时扩容消费者:
- 增加消费者实例数(注意:Kafka 分区数限制了并发度)
- 增加消费者线程池大小
- 临时机器扩容
2. 快速消费跳过非核心逻辑:
java
// 临时降级:跳过非核心处理,仅记录日志
@RocketMQMessageListener(topic = "order_topic")
public class OrderConsumer implements RocketMQListener<String> {
public void onMessage(String msg) {
try {
// 临时降级:仅入库,不调用下游
orderMapper.insert(parse(msg));
// downstreamService.notify(...); // 暂时跳过
} catch (Exception e) {
log.error("消费失败: {}", msg, e);
// 临时改为本地存储,后续补偿
failedMsgMapper.insert(msg);
}
}
}3. 消息转移 + 离线消费:
java
// 方案A:新建一个临时 Topic,用快速消费者把消息搬运过去
// 原消费者只做转发,新消费者慢慢处理
public void onMessage(String msg) {
producer.send("order_topic_backup", msg); // 转发
}
// 方案B:多个消费者分组并行处理(Kafka 增加分区)4. Kafka 专项方案:
bash
# 增加分区数(注意:会破坏顺序性)
kafka-topics.sh --alter --topic order_topic --partitions 64
# 增加消费者实例到与分区数相同
# 重置消费位点(跳过堆积,谨慎使用)
kafka-consumer-groups.sh --reset-offsets --group order_group \
--topic order_topic --to-latest --execute消费失败处理:
java
// 1. 重试 + 死信队列
public void onMessage(String msg) {
try {
process(msg);
} catch (Exception e) {
int retryCount = getRetryCount(msg);
if (retryCount < 3) {
// 延迟重试
producer.send("order_retry_topic", msg, retryCount + 1);
} else {
// 进入死信队列,人工处理
producer.send("order_dlt_topic", msg);
alertService.alert("消息进入死信: " + msg);
}
}
}
// 2. 本地存储 + 定时补偿
public void onMessage(String msg) {
try {
process(msg);
} catch (Exception e) {
failedMsgMapper.insert(msg, e.getMessage()); // 落库
// 定时任务重试 failedMsgMapper 中未处理的消息
}
}预防措施:
- 监控消费滞后(Lag)告警
- 设置合理的重试次数和死信队列
- 消费者幂等设计(避免重试导致重复消费)
- 消费者批量处理提高吞吐
- 合理设置 prefetch(Kafka
fetch.min.bytes、RocketMQpullBatchSize)
15.8 ★线上服务出现连接池耗尽(数据库/Redis/HTTP)如何排查?如何合理设置连接池参数?
📖 点击查看答案
排查步骤:
1. 数据库连接池耗尽:
bash
# 现象:获取连接超时 "Cannot get a connection, pool error timeout"
# 查看 Druid 状态
curl http://app:port/druid/datasource.json
# 关注:activeCount、poolingCount、waitThreadCount
# MySQL 侧查看连接
SHOW PROCESSLIST;
SELECT * FROM information_schema.processlist WHERE db = 'order_db';2. Redis 连接池耗尽:
bash
# 现象:JedisException: Could not get a resource from the pool
redis-cli INFO clients # 当前连接数
redis-cli CONFIG GET maxclients # 最大连接数3. HTTP 连接池耗尽:
bash
# 现象:Timeout waiting for connection from pool
# 查看 HttpClient 连接池状态(需埋点)
# 或 jstack 查看线程是否在等待连接
jstack <pid> | grep -A 5 "POOL"常见原因:
- 连接泄漏:获取连接后未关闭(异常未走 finally)
- 慢查询/慢调用:连接占用时间长
- 并发突增:连接数不足
- 连接池参数配置不当:maxActive 太小、超时太长
合理参数配置:
yaml
# HikariCP(推荐,性能最好)
spring:
datasource:
hikari:
maximum-pool-size: 20 # 最大连接数(公式:核心数 * 2 + 有效磁盘数)
minimum-idle: 10 # 最小空闲连接
connection-timeout: 30000 # 获取连接超时(30s)
idle-timeout: 600000 # 空闲连接超时(10min)
max-lifetime: 1800000 # 连接最大生命周期(30min)
leak-detection-threshold: 60000 # 连接泄漏检测(60s)
# Druid
spring:
datasource:
druid:
initial-size: 5
min-idle: 10
max-active: 20
max-wait: 60000 # 获取连接超时
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
remove-abandoned: true # 回收泄漏连接
remove-abandoned-timeout: 300 # 泄漏超时
log-abandoned: true # 记录泄漏堆栈
# Redis 连接池(Lettuce)
spring:
redis:
lettuce:
pool:
max-active: 50
max-idle: 20
min-idle: 5
max-wait: 3000ms连接数计算公式:
- 数据库连接池大小 ≠ 越大越好
- PostgreSQL 官方建议:
connections = ((core_count * 2) + effective_spindle_count) - 一般 20-50 即可满足大多数场景
- 过大反而增加 DB 负载(上下文切换、锁竞争)
代码层面防泄漏:
java
// 正确:try-with-resources
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
// ...
} // 自动关闭
// 错误:手动关闭易遗漏
Connection conn = dataSource.getConnection();
// ... 异常时未关闭 conn → 泄漏
// ThreadLocal 连接需在 finally 中 remove
ThreadLocal<Connection> tl = new ThreadLocal<>();
try {
tl.set(dataSource.getConnection());
// ...
} finally {
if (tl.get() != null) {
tl.get().close();
tl.remove(); // 必须 remove
}
}预防措施:
- 使用 try-with-resources 自动释放
- 开启连接泄漏检测(Druid
removeAbandoned) - 监控连接池使用率(活跃连接/最大连接 > 80% 告警)
- 合理设置超时(获取连接、SQL 执行、HTTP 调用)
- 排查慢 SQL/慢调用,减少连接占用时间
15.9 ★线上服务突然出现大量 502 / 503 错误,排查思路是什么?
📖 点击查看答案
502 vs 503 区别:
- 502 Bad Gateway:网关/代理收到上游无效响应(上游服务挂了/重启/无响应)
- 503 Service Unavailable:服务暂时不可用(过载、限流、熔断、健康检查失败)
排查思路:
1. 确认影响范围:
- 是单个服务还是多个服务?
- 是部分实例还是全部实例?
- 是持续还是间歇?
2. 502 排查(网关层):
bash
# Nginx 502:后端服务无响应
# 查看 Nginx error.log
tail -f /var/log/nginx/error.log
# 常见:connect() failed (111: Connection refused)
# → 后端服务挂了/未启动/端口不对
# 常见:upstream timed out
# → 后端响应慢,调整 proxy_read_timeout
# 检查后端服务健康
curl http://backend:8080/health
# 查看服务是否存活
ps -ef | grep java
# 查看服务日志
tail -f /app/logs/app.log3. 503 排查(服务层):
bash
# 服务过载
# 查看负载
top # CPU
free -m # 内存
# 查看线程数
jstack <pid> | grep "java.lang.Thread.State" | wc -l
# 查看连接数
netstat -an | grep 8080 | wc -l
# 限流/熔断触发
# 查看限流配置(Sentinel)
curl http://app:port/sentinel/dashboard
# 查看熔断器状态(Hystrix/Resilience4j)
# 健康检查失败导致摘除
# 查看注册中心(Nacos/Eureka)
curl http://nacos:8848/nacos/v1/ns/instance/list?serviceName=xxx4. 分层定位:
用户 → CDN → LB(Nginx) → Gateway → Service → DB/Redis/MQ- LB 层:Nginx 日志、upstream 健康状态
- Gateway 层:网关日志、限流熔断状态
- Service 层:应用日志、GC、线程池、连接池
- 下游层:DB/Redis 是否可用
常见原因:
| 错误码 | 原因 | 解决 |
|---|---|---|
| 502 | 后端服务挂掉 | 重启服务、查看 OOM/崩溃日志 |
| 502 | 后端重启中 | 滚动发布等待启动 |
| 502 | 连接超时 | 调整超时、优化慢接口 |
| 503 | 限流触发 | 扩容、调整限流阈值 |
| 503 | 熔断触发 | 修复下游、调整熔断策略 |
| 503 | 健康检查失败 | 修复健康检查逻辑 |
| 503 | 线程池满 | 增大线程池、优化慢调用 |
应急处理:
- 确认是否在发布(发布中 503 正常)
- 重启异常实例
- 扩容实例数
- 降级非核心功能
- 临时调大限流/超时阈值
- 切换备用机房
15.10 ★服务发布之后出现大量异常,在不重启服务的情况下如何快速止血?如何设计一套完善的线上监控告警体系?
📖 点击查看答案
不重启的快速止血方案:
1. 回滚(最有效):
bash
# 回滚到上一版本(K8s)
kubectl rollout undo deployment/order-service
# 或指定版本
kubectl rollout undo deployment/order-service --to-revision=3
# 查看发布历史
kubectl rollout history deployment/order-service2. 动态配置降级:
java
// 通过配置中心(Nacos/Apollo)动态关闭新功能
@Value("${feature.new_order_flow.enabled:false}")
private boolean newOrderFlowEnabled;
// 热更新配置,无需重启
// Nacos 控制台修改 feature.new_order_flow.enabled = false3. 限流/熔断保护:
java
// Sentinel 动态规则,在控制台实时调整
// 1. 对异常接口加限流规则
// 2. 对下游服务加熔断规则
// 3. 临时关闭异常接口的访问
// Arthas 动态修改日志级别
logger --name com.example --level DEBUG
// 动态查看方法调用
watch com.example.OrderService createOrder "{params, returnObj, throwExp}"4. 流量切换:
- 在网关层将异常实例摘除(修改权重为 0)
- 切换到稳定版本的集群
- 灰度发布:先切少量流量验证
监控告警体系设计:
1. 监控分层:
基础设施 → 中间件 → 应用 → 业务| 层级 | 指标 | 工具 |
|---|---|---|
| 基础设施 | CPU、内存、磁盘、网络 | Prometheus + Node Exporter |
| 中间件 | DB 连接数、慢SQL、Redis 内存、MQ 堆积 | Prometheus + Exporter |
| 应用 | QPS、RT、错误率、GC、线程池 | Micrometer + Prometheus |
| 业务 | 订单量、支付成功率、库存预警 | 自定义指标 + Grafana |
2. 黄金信号(Google SRE):
- 延迟(Latency):P50/P95/P99 响应时间
- 流量(Traffic):QPS、并发数
- 错误(Errors):错误率、5xx 状态码
- 饱和度(Saturation):CPU、内存、连接池使用率
3. 告警规则示例:
yaml
# Prometheus Alertmanager 规则
groups:
- name: app_alerts
rules:
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.05
for: 2m
labels:
severity: critical
annotations:
summary: "错误率超过 5%"
- alert: HighLatency
expr: histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) > 1
for: 5m
labels:
severity: warning
annotations:
summary: "P99 延迟超过 1s"
- alert: JVM_Heap_High
expr: jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"} > 0.85
for: 5m
labels:
severity: warning
- alert: Full_GC_Frequent
expr: increase(jvm_gc_seconds_count{action="end of major GC"}[5m]) > 3
for: 1m
labels:
severity: critical4. 告警分级:
- P0(致命):核心服务不可用 → 电话+短信+IM,立即响应
- P1(严重):错误率飙升/RT 翻倍 → 短信+IM,5 分钟响应
- P2(警告):资源使用率高 → IM,30 分钟处理
- P3(提示):慢查询增多 → 邮件,工作时间处理
5. 监控告警原则:
- 告警要有可操作性:收到告警知道该做什么
- 避免告警风暴:合理设置阈值、静默规则、聚合
- 告警收敛:同源告警合并、关联告警聚合
- 故障自愈:自动重启、自动扩容(高级)
- 故障复盘:每次故障记录 RCA(根因分析)