架构师最值钱的时刻就是线上出问题的时候。本模块覆盖CPU飙高、内存泄漏、慢SQL、全链路追踪、OOM、网络故障等核心排障场景,每道题都是真实的生产案例,考察候选人在高压环境下的系统化排障能力。能不能在凌晨3点被叫醒后30分钟内定位问题,是区分”PPT架构师”和”真架构师”的关键。
难度标记
🔵 高级(Senior):8-10年经验应该能答好
🔴 专家(Expert):需要深入的实战经验和思考
⚫ 大师(Master):开放性设计题,考察架构哲学和权衡能力
一、CPU问题排查(1-10题) 1. 🔵 线上Java服务CPU飙到100%,请描述你的完整排查流程。
答:CPU飙高是最常见的线上问题,需要系统化的排查流程。
标准排查流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 top -c top -Hp 12345 printf "%x\n" 12378jstack 12345 | grep -A 30 "0x305a"
常见CPU飙高原因及对应堆栈特征:
死循环/无限递归:
1 2 3 4 "thread-1" #15 prio=5 RUNNABLE at com.example.Service.process(Service.java:42) at com.example.Service.process(Service.java:45) // 递归 ...
频繁Full GC:
1 2 3 4 5 jstat -gcutil 12345 1000 jmap -histo:live 12345 | head -20
正则表达式灾难性回溯:
1 2 3 4 5 "thread-1" RUNNABLE at java.util.regex.Pattern$GroupHead.match(Pattern.java:4658) at java.util.regex.Pattern$Loop.match(Pattern.java:4785) at java.util.regex.Pattern$GroupTail.match(Pattern.java:4717) // 大量regex相关堆栈
线程上下文切换过多:
1 2 3 4 5 6 vmstat 1 pidstat -w -p 12345 1
2. 🔴 线上服务CPU使用率不高(30%),但接口响应很慢,可能是什么原因?如何排查?
答:CPU不高但响应慢,说明线程大部分时间不在执行计算,而是在等待。
排查思路:
1 2 3 4 5 6 7 jstack 12345 | grep "java.lang.Thread.State" | sort | uniq -c jstack 12345 > thread_dump.txt
常见原因:
锁竞争(BLOCKED):
1 2 3 4 5 6 7 8 "http-nio-8080-exec-1" BLOCKED waiting to lock <0x00000007162a0e80> (a java.lang.Object) at com.example.Service.syncMethod(Service.java:30) "http-nio-8080-exec-2" RUNNABLE locked <0x00000007162a0e80> (a java.lang.Object) at com.example.Service.syncMethod(Service.java:35) // 大量线程等待同一把锁
数据库连接池耗尽(WAITING):
1 2 3 4 5 6 7 8 "http-nio-8080-exec-1" WAITING at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:162) // 等待获取数据库连接 排查: - 检查连接池配置(maximumPoolSize) - 检查是否有慢SQL占用连接 - 检查是否有连接泄漏(获取后未归还)
外部服务调用慢(TIMED_WAITING):
1 2 3 4 5 6 7 8 9 "http-nio-8080-exec-1" TIMED_WAITING at java.net.SocketInputStream.socketRead0(Native Method) at org.apache.http.impl.io.SessionInputBufferImpl.streamRead(...) // 等待外部HTTP响应 排查: - 检查下游服务的响应时间 - 检查网络延迟(ping/traceroute) - 检查是否设置了合理的超时时间
IO等待:
1 2 3 4 5 6 7 iostat -x 1 iotop -p 12345
3. 🔴 如何区分CPU使用率高是由用户态代码还是内核态代码导致的?不同情况的排查方向有什么不同?
答:区分用户态和内核态CPU占用对定位问题方向至关重要。
查看方法:
1 2 3 4 5 6 7 8 9 10 top mpstat -P ALL 1
用户态CPU高(us高):
1 2 3 4 5 6 7 8 9 10 11 原因:应用代码本身的计算密集 排查方向: - jstack看线程堆栈,找到热点代码 - 使用async-profiler做CPU火焰图 ./profiler.sh -d 30 -f cpu.html 12345 - 常见原因: - 死循环、复杂计算 - JSON/XML序列化大对象 - 正则表达式回溯 - 加密解密操作 - GC(GC线程也是用户态)
内核态CPU高(sy高):
1 2 3 4 5 6 7 8 9 10 11 12 13 原因:大量系统调用或内核操作 排查方向: - strace跟踪系统调用 strace -cp 12345 # 统计系统调用分布 strace -T -p 12345 # 查看每个系统调用的耗时 - perf分析内核热点 perf top -p 12345 - 常见原因: - 大量线程创建/销毁(clone/futex系统调用多) - 频繁的内存分配/释放(mmap/munmap多) - 网络IO密集(sendto/recvfrom多) - 锁竞争(futex系统调用多) - 文件描述符操作频繁(open/close/read/write多)
4. 🔴 什么是CPU火焰图(Flame Graph)?如何生成和解读?请描述一次用火焰图定位性能问题的经历。
答:火焰图是Brendan Gregg发明的性能分析可视化工具,能直观展示CPU时间花在哪些函数调用上。
生成方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ./profiler.sh -d 30 -f flame.html -o flamegraph 12345 perf record -g -p 12345 -- sleep 30 perf script | ./stackcollapse-perf.pl | ./flamegraph.pl > flame.svg profiler start profiler stop --format html --file /tmp/flame.html
解读方法:
1 2 3 4 5 6 7 8 9 火焰图的结构: - X轴:函数调用的宽度代表CPU占用比例(越宽占用越多) - Y轴:调用栈深度(从下到上是调用链) - 颜色:无特殊含义,只是为了区分不同函数 关注点: 1. 最宽的"平顶":CPU时间最多的函数 2. 宽且深的调用链:可能是递归或深层调用 3. 意外出现的宽函数:如GC、序列化、加密
实战案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 问题:某服务CPU从20%突然飙到80% 火焰图分析: - 发现com.fasterxml.jackson.databind.ser.std.MapSerializer 占了60%的CPU - 追踪调用链:是一个接口返回了一个巨大的Map(10万个Key) - 每次请求都在序列化这个大Map 根因: - 上游服务变更,返回数据量从100条变成10万条 - 下游服务没有做分页,全量序列化 修复: - 增加分页参数,限制单次返回数据量 - 增加响应体大小监控告警
5. 🔵 Java应用频繁Full GC导致CPU飙高,如何排查和解决?
答:Full GC是Java应用CPU飙高的最常见原因之一。
排查流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 jstat -gcutil 12345 1000 jmap -dump:live,format=b,file=heap.hprof 12345
常见原因及解决方案:
内存泄漏:
1 2 3 4 5 6 7 8 9 10 现象:Old区持续增长,Full GC后回收不了多少 原因: - 静态集合不断添加元素(如static Map) - 未关闭的资源(Connection、Stream) - 监听器/回调未注销 - ThreadLocal未清理 解决: - MAT分析找到泄漏对象 - 修复代码,确保资源正确释放
大对象直接进入Old区:
1 2 3 4 5 6 7 8 9 现象:Young GC正常,但Old区快速填满 原因: - 大数组、大字符串直接分配到Old区 - -XX:PretenureSizeThreshold设置过小 解决: - 检查是否有不合理的大对象分配 - 调整PretenureSizeThreshold - 优化代码,避免创建大对象
堆内存设置不合理:
1 2 3 4 5 6 7 现象:频繁GC但每次都能回收大量空间 原因:堆太小,正常对象就把堆填满了 解决: - 增大堆内存(-Xmx) - 调整Young/Old比例(-XX:NewRatio) - 考虑升级GC算法(G1 → ZGC)
6. 🔴 线上出现CPU毛刺(偶尔飙高然后恢复),如何排查这种间歇性问题?
答:间歇性CPU毛刺比持续性高CPU更难排查,因为问题发生时可能来不及抓取现场。
排查策略:
持续监控 + 自动抓取:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 */10 * * * * jstack $(pgrep -f myapp) > /tmp/jstack_$(date +%s).txt while true ; do cpu=$(top -bn1 -p 12345 | tail -1 | awk '{print $9}' ) if (( $(echo "$cpu > 80 " | bc -l) )); then timestamp=$(date +%Y%m%d_%H%M%S) jstack 12345 > /tmp/jstack_${timestamp} .txt jmap -histo 12345 > /tmp/jmap_${timestamp} .txt echo "CPU spike detected: ${cpu} % at ${timestamp} " fi sleep 5 done ./profiler.sh -d 3600 -f profile.jfr 12345
常见毛刺原因:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 a. GC停顿: - CMS的Remark阶段或G1的Mixed GC - 查看GC日志中的停顿时间 - 解决:调优GC参数或升级到ZGC b. JIT编译: - 热点代码触发JIT编译,编译期间CPU飙高 - 查看:-XX:+PrintCompilation - 解决:预热(Warm-up)或AOT编译 c. 定时任务: - 某个定时任务周期性执行重计算 - 查看:crontab和应用内的@Scheduled - 解决:优化定时任务或错峰执行 d. 缓存过期风暴: - 大量缓存同时过期,触发重建 - 解决:缓存过期时间加随机偏移
7. 🔵 如何使用Arthas进行线上Java应用的CPU问题诊断?
答:Arthas是阿里开源的Java诊断工具,无需重启应用即可诊断问题。
常用CPU诊断命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 java -jar arthas-boot.jar dashboard thread -n 5 thread -b thread --state BLOCKED profiler start profiler status profiler stop --format html --file /tmp/flame.html trace com.example.OrderService createOrder watch com.example.OrderService createOrder '{params, returnObj, throwExp}' -x 3 monitor com.example.OrderService createOrder -c 10
实战技巧:
1 2 3 4 5 6 7 8 9 10 11 12 13 场景:某接口偶尔超时 # 用trace找到慢的子方法 trace com.example.OrderService createOrder '#cost > 1000' # 只显示耗时>1秒的调用 # 发现是queryOrder方法慢 trace com.example.OrderMapper queryOrder '#cost > 500' # 继续下钻,发现是SQL执行慢 # 用watch查看慢SQL的参数 watch com.example.OrderMapper queryOrder '{params}' '#cost > 500' -x 3 # 发现某个参数导致全表扫描
8. 🔴 容器化环境(K8s Pod)中的CPU问题排查和物理机有什么不同?需要注意什么?
答:容器环境的CPU排查有几个关键差异。
差异1:CPU限制(CGroup)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 问题:容器设置了CPU limit,但top显示的CPU使用率是宿主机视角 # 容器内看到的CPU核数可能是宿主机的核数,不是limit的核数 # 例如:limit=2核,但容器内nproc显示64核 # 正确查看容器CPU限制 cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us # CPU配额(微秒) cat /sys/fs/cgroup/cpu/cpu.cfs_period_us # CPU周期(微秒) # quota/period = 可用CPU核数 # 例如:200000/100000 = 2核 # JVM需要感知容器CPU限制 # JDK 8u191+/JDK 10+自动识别 # 旧版本需要手动设置:-XX:ActiveProcessorCount=2
差异2:CPU Throttling
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 问题:容器CPU使用率看起来不高,但响应慢 原因:CPU Throttling(节流) - 容器在一个周期内用完了CPU配额,被CGroup暂停 - 表现为间歇性延迟 检查: cat /sys/fs/cgroup/cpu/cpu.stat # nr_throttled: 被节流的次数 # throttled_time: 被节流的总时间(纳秒) # Kubernetes中查看 kubectl top pod <pod-name> # 如果CPU使用率接近limit,很可能在被throttle 解决: - 增加CPU limit - 优化代码减少CPU使用 - 设置合理的request和limit比例(建议limit = 2×request)
差异3:工具可用性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 容器镜像通常是精简的,缺少诊断工具 解决方案: 1. 使用kubectl exec进入容器后安装工具 kubectl exec -it <pod> -- bash apt-get update && apt-get install -y procps 2. 使用临时调试容器(Ephemeral Container) kubectl debug -it <pod> --image=busybox --target=<container> 3. 使用nsenter从宿主机进入容器的命名空间 nsenter -t <pid> -m -u -i -n -p -- jstack <java-pid> 4. 预置诊断工具到基础镜像 FROM openjdk:17-slim RUN apt-get update && apt-get install -y procps net-tools
9. 🔴 如何排查Java应用的线程死锁?
答:死锁是线程互相等待对方持有的锁,导致所有相关线程永久阻塞。
排查方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 jstack 12345 thread -b ThreadMXBean tmx = ManagementFactory.getThreadMXBean(); long[] deadlockedThreads = tmx.findDeadlockedThreads(); if (deadlockedThreads != null) { ThreadInfo[] infos = tmx.getThreadInfo(deadlockedThreads, true , true ); // 记录死锁信息并告警 }
常见死锁模式:
10. 🔵 什么是Java的安全点(Safepoint)?它如何影响应用性能?如何排查Safepoint导致的延迟?
答:Safepoint是JVM中所有线程都暂停的点,GC、偏向锁撤销等操作需要在Safepoint执行。
Safepoint的影响:
1 2 3 4 5 6 7 8 9 10 11 问题:即使GC本身很快(如Young GC只需5ms), 但等待所有线程到达Safepoint可能需要很长时间 场景: - 某个线程在执行一个大循环(如遍历大数组) - JVM需要等这个线程到达Safepoint(循环回边或方法返回) - 如果循环体内没有Safepoint,其他所有线程都要等待 JDK 8的问题: - int类型的循环计数器,JIT编译后可能不插入Safepoint - 导致长时间无法到达Safepoint(称为"Safepoint bias")
排查方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 -XX:+PrintGCApplicationStoppedTime -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 -Xlog:safepoint=info
解决方案:
1 2 3 4 1. 避免大循环中使用int计数器(用long代替) 2. 在大循环中手动插入Safepoint:Thread.yield() 3. JDK 17+:-XX:+UseCountedLoopSafepoints(默认开启) 4. 使用ZGC/Shenandoah:大幅减少需要Safepoint的场景
二、内存问题排查(11-20题) 11. 🔵 Java应用发生OOM(OutOfMemoryError),有哪几种类型?分别是什么原因?
答:Java的OOM不只是堆内存溢出,有多种类型,排查方向完全不同。
OOM类型:
java.lang.OutOfMemoryError: Java heap space
1 2 3 4 5 6 7 8 原因:堆内存不足 - 内存泄漏:对象持续创建但无法被GC回收 - 大对象:一次性加载大量数据到内存(如全表查询) - 堆设置过小 排查: - jmap -dump:live,format=b,file=heap.hprof <pid> - MAT分析Dominator Tree和Leak Suspects
java.lang.OutOfMemoryError: Metaspace
1 2 3 4 5 6 7 8 9 原因:元空间(类元数据)不足 - 动态生成大量类(如CGLIB代理、Groovy脚本、JSP) - 类加载器泄漏(热部署场景常见) - Metaspace设置过小 排查: - jstat -gcutil查看M(Metaspace)使用率 - jcmd <pid> VM.classloader_stats - -XX:MaxMetaspaceSize=512m 设置上限
java.lang.OutOfMemoryError: GC overhead limit exceeded
1 2 3 4 原因:GC花费了98%以上的时间,但只回收了不到2%的堆内存 - 本质上还是堆内存不足,但JVM提前报错避免无意义的GC 排查:同heap space
java.lang.OutOfMemoryError: Direct buffer memory
1 2 3 4 5 6 7 8 原因:直接内存(堆外内存)不足 - NIO的ByteBuffer.allocateDirect() - Netty的PooledByteBufAllocator - 直接内存不受-Xmx限制,受-XX:MaxDirectMemorySize限制 排查: - jcmd <pid> VM.native_memory summary - Netty的内存泄漏检测:-Dio.netty.leakDetection.level=PARANOID
java.lang.OutOfMemoryError: unable to create new native thread
1 2 3 4 5 6 7 8 9 原因:无法创建新的操作系统线程 - 线程数达到操作系统限制 - 每个线程默认占用1MB栈空间(-Xss) - ulimit -u限制了用户最大进程数 排查: - 查看线程数:jstack <pid> | grep "java.lang.Thread.State" | wc -l - 查看系统限制:ulimit -u, cat /proc/sys/kernel/threads-max - 检查是否有线程泄漏(线程池未正确关闭)
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
1 2 原因:尝试创建超大数组(接近Integer.MAX_VALUE) - 通常是代码Bug,如错误的数组大小计算
12. 🔴 如何排查Java应用的内存泄漏?请描述完整的排查流程。
答:内存泄漏排查是一个系统化的过程,需要结合多种工具。
排查流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 Step 1:确认是否存在内存泄漏 - 监控堆内存使用趋势(Grafana/Prometheus) - 如果Full GC后Old区使用量持续上升 → 确认泄漏 jstat -gcutil <pid> 5000 # 观察每次Full GC后O(Old区)的使用率 # 如果每次GC后O都比上次高 → 泄漏 Step 2:获取堆快照 # 方法1:jmap(会触发Full GC,线上慎用) jmap -dump:live,format=b,file=heap1.hprof <pid> # 等待一段时间后再dump一次 jmap -dump:live,format=b,file=heap2.hprof <pid> # 方法2:OOM时自动dump(推荐,提前配置) -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/logs/heap.hprof Step 3:MAT分析 1. 打开heap.hprof 2. 查看Leak Suspects Report(自动分析) 3. 查看Dominator Tree(按Retained Heap排序) - Retained Heap:对象被GC后能释放的总内存 - 找到Retained Heap最大的对象 4. 右键 → Path to GC Roots → exclude weak/soft references - 找到泄漏对象的引用链 - 确定是谁持有了不该持有的引用 Step 4:对比两次dump - MAT支持对比两次dump的Histogram - 找到数量增长最多的类 - 这些类很可能就是泄漏的对象
常见内存泄漏模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 private static final Map<String, Object> cache = new HashMap <>();public void process (String key, Object value) { cache.put(key, value); } public void query () { Connection conn = dataSource.getConnection(); ResultSet rs = conn.prepareStatement(sql).executeQuery(); } eventBus.register(this ); private static ThreadLocal<List<Object>> threadLocal = new ThreadLocal <>();public void process () { threadLocal.set(new ArrayList <>()); }
13. 🔴 堆外内存泄漏如何排查?和堆内存泄漏的排查有什么不同?
答:堆外内存泄漏更难排查,因为常规的jmap/MAT无法看到堆外内存。
堆外内存的来源:
1 2 3 4 5 6 1. DirectByteBuffer:NIO直接内存 2. JNI:本地方法分配的内存 3. 线程栈:每个线程的栈空间(-Xss) 4. Metaspace:类元数据 5. Code Cache:JIT编译后的代码 6. 第三方native库:如Netty的PooledByteBufAllocator
排查方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 jcmd <pid> VM.native_memory summary jcmd <pid> VM.native_memory baseline jcmd <pid> VM.native_memory summary.diff pmap -x <pid> | sort -k3 -n -r | head -20 LD_PRELOAD=/usr/lib/libjemalloc.so MALLOC_CONF=prof:true java -jar app.jar
14. 🔵 什么是Java的内存模型(JMM)相关的线上问题?如何排查可见性和有序性Bug?
答:JMM相关的Bug是最难排查的线上问题之一,因为它们通常是间歇性的、不可复现的。
常见JMM问题:
可见性问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private boolean running = true ; public void stop () { running = false ; } public void run () { while (running) { doWork(); } }
指令重排序问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private static Singleton instance; public static Singleton getInstance () { if (instance == null ) { synchronized (Singleton.class) { if (instance == null ) { instance = new Singleton (); } } } return instance; }
排查方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 1. 代码审查: - 检查多线程共享变量是否有volatile或synchronized保护 - 检查是否有DCL模式缺少volatile - 检查long/double类型的非原子读写 2. 工具检测: - FindBugs/SpotBugs:静态分析,检测并发Bug - ThreadSanitizer(C/C++):运行时检测数据竞争 - jcstress:Java并发压力测试框架(OpenJDK出品) 3. 压测复现: - 增加并发线程数 - 在关键位置插入Thread.yield()增加调度随机性 - 使用jcstress编写并发测试用例
15. 🔴 Linux的OOM Killer是什么?它如何选择要杀死的进程?如何保护关键进程不被OOM Killer杀死?
答:OOM Killer是Linux内核的内存保护机制,当系统物理内存耗尽时,内核会选择一个进程杀死以释放内存。
OOM Killer的选择算法:
1 2 3 4 5 6 7 8 内核为每个进程计算一个oom_score(/proc/<pid>/oom_score): - 基础分数:进程使用的内存越多,分数越高 - 调整因子:oom_score_adj(-1000 ~ 1000) - -1000:永远不会被OOM Killer杀死 - 0:默认值 - 1000:优先被杀死 OOM Killer选择oom_score最高的进程杀死
保护关键进程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 echo -1000 > /proc/<pid>/oom_score_adj[Service] OOMScoreAdjust=-1000 echo 2 > /proc/sys/vm/overcommit_memory
排查OOM Kill:
1 2 3 4 5 6 7 8 dmesg | grep -i "oom\|killed" journalctl -k | grep -i oom
16. 🔴 如何监控和排查Java应用的堆外内存(Off-Heap)使用情况?Netty的内存池是如何管理的?
答:堆外内存是Java应用内存管理中容易被忽视的部分。
Netty内存池架构:
1 2 3 4 5 6 7 8 9 10 11 12 13 PooledByteBufAllocator ├── PoolArena(线程绑定,减少竞争) │ ├── PoolChunk(16MB,向OS申请的内存块) │ │ ├── PoolSubpage(用于小内存分配,<8KB) │ │ └── 伙伴算法(用于大内存分配) │ └── PoolChunk... └── PoolArena... 内存分配策略: - Tiny(<512B):从PoolSubpage分配 - Small(512B~8KB):从PoolSubpage分配 - Normal(8KB~16MB):从PoolChunk用伙伴算法分配 - Huge(>16MB):直接分配,不池化
监控方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 PooledByteBufAllocator allocator = PooledByteBufAllocator.DEFAULT;PooledByteBufAllocatorMetric metric = allocator.metric();long usedDirectMemory = metric.usedDirectMemory();long usedHeapMemory = metric.usedHeapMemory();for (PoolArenaMetric arena : metric.directArenas()) { System.out.println("Active allocations: " + arena.numActiveAllocations()); System.out.println("Active bytes: " + arena.numActiveBytes()); } Gauge.builder("netty.direct.memory.used" , allocator, a -> a.metric().usedDirectMemory()) .register(meterRegistry);
常见堆外内存泄漏场景:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ByteBuf buf = allocator.buffer(1024 );public void channelRead (ChannelHandlerContext ctx, Object msg) { ByteBuf buf = (ByteBuf) msg; }
17. 🔵 什么是内存碎片?在Java和Linux层面分别如何处理?
答:内存碎片是指可用内存被分割成大量不连续的小块,导致无法分配大块连续内存。
Java层面:
1 2 3 4 5 6 7 8 9 10 11 JVM堆内存碎片: - CMS收集器:标记-清除算法,不压缩,容易产生碎片 - 表现:堆空间充足但无法分配大对象 → Full GC - 解决:-XX:+UseCMSCompactAtFullCollection(Full GC时压缩) - 或升级到G1/ZGC(基于Region,碎片问题小) - G1收集器:基于Region,通过Mixed GC回收碎片化严重的Region - 碎片问题比CMS小很多 - 但Region内部仍可能有碎片 - ZGC/Shenandoah:并发压缩,几乎无碎片问题
Linux层面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 cat /proc/buddyinfoecho 1 > /proc/sys/vm/compact_memorycat /sys/kernel/mm/transparent_hugepage/enabledecho never > /sys/kernel/mm/transparent_hugepage/enabled
18. 🔴 如何设计一个Java应用的内存监控告警方案?需要监控哪些指标?
答:完善的内存监控是预防OOM的关键。
监控指标体系:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 一、JVM堆内存: - 堆总使用率(used/max):>80%告警 - Old区使用率:>85%告警 - Full GC频率:>1次/分钟告警 - Full GC后Old区回收率:<10%告警(可能泄漏) - Young GC耗时P99:>100ms告警 二、JVM非堆内存: - Metaspace使用率:>90%告警 - Direct Memory使用量:接近MaxDirectMemorySize告警 - 线程数:>1000告警 - 线程栈总内存:线程数 × Xss 三、操作系统内存: - 物理内存使用率:>90%告警 - Swap使用量:>0告警(Java应用不应该使用Swap) - OOM Kill事件:任何OOM Kill立即告警 四、容器内存(K8s): - 容器内存使用率(相对于limit):>85%告警 - 容器OOM Kill次数 - 内存Request vs 实际使用(用于优化资源配置)
Prometheus + Grafana实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 - jvm_memory_used_bytes{area="heap"} - jvm_memory_used_bytes{area="nonheap"} - jvm_gc_pause_seconds_count{cause="Allocation Failure"} - jvm_gc_pause_seconds_sum - jvm_threads_live_threads - jvm_buffer_memory_used_bytes{id="direct"} groups: - name: jvm-memory rules: - alert: HeapMemoryHigh expr: jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"} > 0.85 for: 5m labels: severity: warning - alert: FrequentFullGC expr: rate(jvm_gc_pause_seconds_count{action="end of major GC"}[5m]) > 0.2 for: 5m labels: severity: critical
19. 🔴 Java应用使用了大量内存但jmap显示堆使用率不高,可能是什么原因?
答:这是一个经典的”内存去哪了”问题,需要从堆外内存角度排查。
可能的原因:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 1. 直接内存(Direct Memory): - NIO的ByteBuffer.allocateDirect() - Netty的堆外内存池 - 检查:jcmd <pid> VM.native_memory summary 2. 线程栈: - 每个线程默认1MB栈空间 - 1000个线程 = 1GB - 检查:jstack <pid> | grep "java.lang.Thread.State" | wc -l 3. Metaspace: - 大量动态生成的类 - 检查:jstat -gcutil <pid>,看M列 4. Code Cache: - JIT编译后的代码缓存 - 默认240MB(JDK 8+) - 检查:jcmd <pid> Compiler.codecache 5. JNI/Native库: - 第三方native库分配的内存 - 如RocksDB、LevelDB、OpenSSL - 检查:pmap -x <pid> 6. 内存映射文件(mmap): - MappedByteBuffer - 如Kafka的日志文件、RocketMQ的CommitLog - 检查:pmap -x <pid> | grep map 7. GC自身的开销: - G1的Remember Set、Card Table - ZGC的染色指针需要额外内存
完整排查命令:
1 2 3 4 5 6 7 8 9 10 11 12 ps -p <pid> -o rss,vsz jcmd <pid> VM.native_memory detail
20. ⚫ 如何设计一个能自动检测和处理内存泄漏的系统?
答:自动化内存泄漏检测是大规模微服务架构的刚需。
系统设计:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 架构: Agent(每个JVM实例)→ 数据收集 → 分析引擎 → 告警/自愈 1. Agent层: - 定期采集JVM内存指标(每10秒) - Full GC后自动记录Old区使用量 - 内存超过阈值时自动dump堆快照(限制频率) 2. 分析引擎: - 趋势分析:连续N次Full GC后Old区使用量递增 → 疑似泄漏 - 对比分析:同一服务的不同实例,内存差异过大 → 疑似泄漏 - 版本对比:新版本上线后内存增长速率变化 → 可能引入泄漏 3. 自动处理: Level 1:告警通知(钉钉/Slack) Level 2:自动dump堆快照并上传到分析平台 Level 3:自动重启泄漏实例(K8s Pod重建) Level 4:自动回滚到上一个版本(如果是新版本引入的) 4. 分析平台: - 自动分析堆快照,生成泄漏报告 - 对比不同时间点的快照,找出增长最快的对象 - 展示泄漏对象的引用链
三、慢SQL与数据库问题排查(21-30题) 21. 🔵 线上出现慢SQL告警,请描述你的完整排查和优化流程。
答:慢SQL是最常见的性能问题之一,需要系统化的排查流程。
排查流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 SHOW PROCESSLIST;SELECT * FROM performance_schema.events_statements_summary_by_digestORDER BY SUM_TIMER_WAIT DESC LIMIT 10 ;EXPLAIN SELECT * FROM orders WHERE user_id = 123 AND status = 1 ; SHOW INDEX FROM orders;
常见慢SQL原因及优化:
缺少索引或索引失效:
1 2 3 4 5 6 7 8 9 10 11 WHERE YEAR (create_time) = 2024 WHERE name LIKE '%张%' WHERE status != 1 WHERE user_id = 123 OR status = 1
大表JOIN:
1 2 3 4 SELECT * FROM orders o JOIN order_items oi ON o.id = oi.order_idWHERE o.create_time > '2024-01-01' ;
深分页:
1 2 3 4 SELECT * FROM orders ORDER BY id LIMIT 1000000 , 20 ;SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 20 ;
22. 🔴 MySQL的锁等待和死锁如何排查?如何预防?
答:锁问题是数据库性能的隐形杀手。
排查锁等待:
1 2 3 4 5 6 7 8 9 10 11 SELECT * FROM information_schema.INNODB_LOCK_WAITS;SELECT * FROM performance_schema.data_lock_waits;SELECT * FROM information_schema.INNODB_TRX;SELECT * FROM performance_schema.data_locks;
排查死锁:
1 2 3 4 5 6 7 8 9 SHOW ENGINE INNODB STATUS\GSET GLOBAL innodb_print_all_deadlocks = ON ;
死锁预防:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 1. 按固定顺序访问表和行 -- 反模式:事务1先更新A再更新B,事务2先更新B再更新A -- 正确:所有事务都按ID排序后更新 2. 缩短事务时间 -- 事务中不要包含RPC调用、文件IO等耗时操作 -- 尽快提交或回滚 3. 使用合理的隔离级别 -- RC(Read Committed)比RR(Repeatable Read)产生的锁更少 -- RR下的间隙锁是死锁的常见原因 4. 添加合理的索引 -- 没有索引时,UPDATE/DELETE会锁全表 -- 有索引时,只锁匹配的行
23. 🔴 如何排查MySQL的连接数暴涨问题?
答:连接数暴涨通常意味着应用层出了问题。
排查流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 SHOW STATUS LIKE 'Threads_connected' ;SHOW VARIABLES LIKE 'max_connections' ;SELECT user , host, db, command, time , state, info FROM information_schema.processlist ORDER BY time DESC ;SELECT SUBSTRING_INDEX(host, ':' , 1 ) AS client_ip, COUNT (* ) AS conn_countFROM information_schema.processlistGROUP BY client_ip ORDER BY conn_count DESC ;SELECT command, COUNT (* ) FROM information_schema.processlist GROUP BY command;
常见原因:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 1. 慢SQL导致连接堆积: - 一个慢SQL占用连接10秒 - QPS=100 → 同时需要1000个连接 - 解决:优化慢SQL 2. 连接池配置不当: - 连接池maxSize过大 - 10个服务实例 × 每个50连接 = 500连接 - 解决:合理设置连接池大小 - 推荐公式:connections = (core_count * 2) + effective_spindle_count 3. 连接泄漏: - 获取连接后未归还(异常路径未关闭) - HikariCP配置:leakDetectionThreshold=60000(60秒未归还告警) 4. 突发流量: - 大促、爬虫等导致请求暴增 - 解决:限流 + 连接池排队
24. 🔴 数据库主从延迟如何排查和处理?
答:主从延迟是读写分离架构中最常见的问题。
排查方法:
1 2 3 4 5 6 7 8 9 10 11 SHOW SLAVE STATUS\Gpt- heartbeat pt- heartbeat
常见延迟原因及处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 1. 从库单线程回放(MySQL 5.6之前): - 主库并行写入,从库单线程回放 - 解决:升级到MySQL 5.7+,开启并行复制 slave_parallel_type = LOGICAL_CLOCK slave_parallel_workers = 8 2. 大事务: - 主库一个大事务执行5分钟 - 从库也需要5分钟回放 - 解决:拆分大事务 3. 从库负载过高: - 从库承担大量读请求,CPU/IO打满 - 回放线程得不到资源 - 解决:增加从库数量,分散读压力 4. 网络延迟: - 主从之间网络带宽不足或延迟高 - 解决:检查网络,考虑半同步复制 业务层处理延迟: - 写后读走主库(通过Hint或中间件路由) - 关键业务强制走主库 - 非关键业务容忍延迟(如评论列表)
25. 🔵 如何排查MySQL的磁盘IO问题?
答:磁盘IO是数据库性能的关键瓶颈。
1 2 3 4 5 6 7 8 9 10 11 12 13 iostat -x 1 iotop -o
1 2 3 4 5 6 7 8 9 10 11 12 13 SHOW ENGINE INNODB STATUS\GSHOW STATUS LIKE 'Innodb_buffer_pool_read%' ;SELECT * FROM performance_schema.events_statements_summary_by_digestORDER BY SUM_ROWS_EXAMINED DESC LIMIT 10 ;
优化方向:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 1. 增大Buffer Pool: innodb_buffer_pool_size = 物理内存的60-80% 2. 优化SQL减少IO: - 添加索引减少全表扫描 - 避免SELECT * - 使用覆盖索引 3. 使用SSD: - 随机IO性能提升100倍+ 4. 调整刷盘策略: innodb_flush_log_at_trx_commit = 2 -- 每秒刷盘(牺牲少量持久性换性能) sync_binlog = 100 -- 每100个事务刷一次binlog
26. 🔴 线上数据库突然变慢,但SQL和数据量都没变化,可能是什么原因?
答:这种”什么都没变但突然变慢”的场景,通常是环境因素导致。
排查清单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 1. 统计信息过期: - MySQL优化器依赖统计信息选择执行计划 - 统计信息过期可能导致优化器选错索引 - 检查:SHOW INDEX FROM table_name; -- Cardinality是否准确 - 修复:ANALYZE TABLE table_name; 2. Buffer Pool被冲刷: - 一个大查询(如全表扫描)把热数据从Buffer Pool中挤出 - 后续查询都需要从磁盘读取 - 检查:Buffer Pool命中率突然下降 - 预防:innodb_old_blocks_time = 1000(防止全表扫描污染Buffer Pool) 3. 锁竞争加剧: - 某个长事务持有锁,导致其他事务等待 - 检查:SHOW ENGINE INNODB STATUS中的TRANSACTIONS部分 - 修复:找到并终止长事务 4. 磁盘IO抖动: - 同一物理机上的其他服务抢占IO - 磁盘坏道导致IO延迟增加 - 检查:iostat -x 1 5. 网络问题: - 应用到数据库的网络延迟增加 - 检查:ping/traceroute 6. 操作系统层面: - Swap被使用(内存不足) - 透明大页(THP)导致延迟抖动 - 检查:free -h, cat /sys/kernel/mm/transparent_hugepage/enabled 7. MySQL内部维护操作: - InnoDB的Purge线程清理undo log - Change Buffer合并 - 自适应哈希索引重建
27. 🔴 如何设计一个完善的SQL审核和慢SQL治理体系?
答:SQL治理需要从开发到运行的全生命周期管理。
全生命周期治理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 一、开发阶段 - SQL审核: 工具:Yearning、Archery、SQLAdvisor 审核规则: - 禁止SELECT * - 禁止不带WHERE的UPDATE/DELETE - 必须有合适的索引 - JOIN不超过3张表 - 子查询改写为JOIN - 禁止在索引列上使用函数 - 分页必须有合理的LIMIT 流程: 开发提交SQL → 自动审核 → DBA人工审核 → 执行 二、测试阶段 - 性能验证: - 使用生产数据量级的测试环境 - 对关键SQL做EXPLAIN分析 - 压测验证SQL在高并发下的表现 三、运行阶段 - 慢SQL监控: - 慢查询日志 + 定期分析(pt-query-digest) - APM系统实时监控SQL耗时(SkyWalking/Pinpoint) - 告警规则: - 单条SQL > 1秒 - 同一SQL 5分钟内出现10次以上慢查询 - 全表扫描次数突增 四、优化阶段 - 持续治理: - 每周慢SQL Top10报告 - 按影响面排序(慢SQL × 调用频率 = 总影响时间) - 优先优化影响面最大的SQL - 建立SQL优化知识库,避免重复问题
28. 🔵 什么是MySQL的查询缓存(Query Cache)?为什么MySQL 8.0移除了它?
答:Query Cache是MySQL缓存SELECT结果的机制,但在高并发场景下反而成为性能瓶颈。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 工作原理: - 对SELECT语句做哈希,缓存结果集 - 相同的SQL直接返回缓存结果 - 表数据有任何修改 → 该表所有缓存失效 为什么被移除: 1. 全局互斥锁: - 查询缓存使用一把全局锁 - 高并发下锁竞争严重 - 即使缓存命中,获取锁的开销也很大 2. 失效粒度太粗: - 表级失效,任何写操作都清空该表所有缓存 - 写多读少的场景,缓存命中率极低 - 频繁的缓存失效和重建反而浪费CPU 3. 内存管理问题: - 缓存碎片化 - 大结果集占用大量内存 替代方案: - 应用层缓存(Redis/Memcached) - ProxySQL的查询缓存(更智能的缓存策略) - InnoDB Buffer Pool(数据页级别的缓存,更高效)
29. 🔴 分库分表后的SQL排查有什么特殊之处?
答:分库分表增加了SQL排查的复杂度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 特殊挑战: 1. SQL路由问题: - SQL被路由到了错误的分片 - 或者没有带分片键,导致全分片扫描 排查: - 检查中间件(ShardingSphere/MyCat)的路由日志 - 确认SQL是否包含分片键 - EXPLAIN在中间件层面查看路由结果 2. 跨分片查询: - ORDER BY + LIMIT在多个分片上执行后需要归并排序 - 深分页问题被放大(每个分片都要扫描大量数据) 排查: - 检查中间件的归并日志 - 查看每个分片的执行时间 3. 分片数据倾斜: - 某个分片数据量远大于其他分片 - 该分片的查询性能差 排查: - 统计各分片的数据量和查询耗时 - 检查分片键的分布是否均匀 4. 全局表的一致性: - 广播表(如配置表)在所有分片都有副本 - 更新时需要保证所有分片一致 排查: - 对比各分片的广播表数据 - 检查是否有更新失败的分片
30. ⚫ 如何设计一个数据库性能自动诊断系统?
答:自动诊断系统是DBA效率提升的关键。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 系统架构: 数据采集层: ├── 慢查询日志采集(Filebeat → Kafka) ├── Performance Schema指标采集(Prometheus) ├── 系统指标采集(node_exporter) └── 中间件指标采集(ShardingSphere metrics) 分析引擎: ├── 实时分析: │ ├── 慢SQL自动EXPLAIN │ ├── 索引推荐(基于查询模式分析) │ ├── 锁等待检测 │ └── 连接数异常检测 │ ├── 离线分析: │ ├── SQL指纹聚合(相似SQL归类) │ ├── 索引使用率分析(未使用的索引建议删除) │ ├── 表空间增长趋势预测 │ └── 分片均衡度分析 │ └── 智能诊断: ├── 根因分析:CPU高→锁等待→慢SQL→缺少索引 ├── 优化建议:自动生成索引建议、SQL改写建议 └── 容量预警:基于增长趋势预测磁盘/连接数不足 展示层: ├── 实时大盘(Grafana) ├── 诊断报告(每日/每周) ├── 告警通知(钉钉/Slack/PagerDuty) └── 一键优化(自动执行低风险优化,如ANALYZE TABLE)
四、全链路追踪与可观测性(31-40题) 31. 🔵 请解释可观测性的三大支柱:Metrics、Logging、Tracing,它们之间的关系是什么?
答:可观测性三大支柱各有侧重,互相补充。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 Metrics(指标): - 回答"发生了什么" - 数值型时间序列数据 - 适合告警和趋势分析 - 示例:QPS、延迟P99、错误率、CPU使用率 - 工具:Prometheus、InfluxDB、Datadog Logging(日志): - 回答"为什么发生" - 离散的事件记录 - 适合排查具体问题 - 示例:错误堆栈、请求参数、业务日志 - 工具:ELK(Elasticsearch+Logstash+Kibana)、Loki Tracing(追踪): - 回答"在哪里发生" - 请求在分布式系统中的完整调用链 - 适合定位跨服务的性能瓶颈 - 示例:一个请求经过API Gateway→OrderService→StockService→DB的完整链路 - 工具:Jaeger、Zipkin、SkyWalking 三者的关系: Metrics发现问题 → Tracing定位到具体服务和接口 → Logging查看详细原因 示例: 1. Metrics告警:订单服务P99延迟从50ms飙到2s 2. Tracing分析:发现是库存服务的deductStock接口慢(1.8s) 3. Logging查看:库存服务日志显示SQL执行超时,连接池等待 4. 进一步排查:数据库慢查询日志找到具体的慢SQL
32. 🔴 如何设计一个分布式追踪系统?核心数据模型是什么?
答:分布式追踪的核心是在请求的整个生命周期中传递上下文。
核心数据模型(OpenTelemetry标准):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Trace(追踪): - 一个完整请求的调用链 - 由一个全局唯一的TraceID标识 Span(跨度): - 调用链中的一个操作单元 - 包含:SpanID、ParentSpanID、操作名、开始时间、结束时间、标签、日志 - Span之间通过ParentSpanID形成树形结构 示例: TraceID: abc123 ├── Span A: API Gateway (SpanID: 1, Parent: null, 0-100ms) │ ├── Span B: OrderService.createOrder (SpanID: 2, Parent: 1, 5-95ms) │ │ ├── Span C: StockService.deduct (SpanID: 3, Parent: 2, 10-60ms) │ │ │ └── Span D: MySQL.query (SpanID: 4, Parent: 3, 15-55ms) │ │ └── Span E: PayService.pay (SpanID: 5, Parent: 2, 65-90ms)
上下文传播:
1 2 3 4 5 6 7 8 9 10 HTTP传播(W3C Trace Context标准): traceparent: 00-abc123-span1-01 tracestate: vendor=value gRPC传播: 通过Metadata传递TraceID和SpanID MQ传播: 将TraceID写入消息Header 消费者从Header中提取TraceID,创建新Span并关联
架构设计:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 应用层(SDK/Agent): - 自动埋点:拦截HTTP/RPC/DB调用,自动创建Span - 手动埋点:业务关键节点手动创建Span - 采样策略:头部采样(决定是否追踪整个请求) 数据传输层: - Agent → Collector(批量发送,减少网络开销) - 协议:OTLP(OpenTelemetry Protocol) - 缓冲:本地队列,防止Collector不可用时丢数据 存储层: - 热数据:Elasticsearch/ClickHouse(最近7天,支持快速查询) - 冷数据:对象存储(历史数据,低成本) - 索引:TraceID、ServiceName、OperationName、Duration、Tags 查询层: - 按TraceID查询完整调用链 - 按服务/接口查询延迟分布 - 拓扑图:自动生成服务依赖关系
33. 🔴 分布式追踪的采样策略有哪些?如何在数据完整性和性能开销之间取得平衡?
答:全量采集在高QPS场景下不现实,采样是必须的。
采样策略:
头部采样(Head-based Sampling):
1 2 3 4 5 在请求入口决定是否采样 - 固定比例:每100个请求采样1个(1%) - 速率限制:每秒最多采样100个请求 - 优点:简单,开销小 - 缺点:可能错过异常请求(异常请求也可能不被采样)
尾部采样(Tail-based Sampling):
1 2 3 4 5 6 7 8 9 10 11 12 在请求完成后,根据结果决定是否保留 - 错误请求:100%保留 - 慢请求(>1s):100%保留 - 正常请求:1%采样 实现: - Collector收集完整Trace后做决策 - 需要在Collector中缓存一段时间的Span数据 - OpenTelemetry Collector支持tail_sampling processor 优点:不会错过异常请求 缺点:Collector需要更多内存和CPU
自适应采样:
1 2 3 4 5 6 7 8 根据系统负载动态调整采样率 - 系统空闲时:采样率高(如10%) - 系统繁忙时:采样率低(如0.1%) - 保证在任何负载下都有足够的采样数据 实现: - 基于当前QPS和Collector处理能力动态计算 - Jaeger的Adaptive Sampling
关键路径采样:
1 2 3 4 对不同的接口/服务使用不同的采样率 - 核心交易链路:10%采样 - 内部管理接口:0.1%采样 - 健康检查接口:不采样
34. 🔴 SkyWalking和Jaeger的架构有什么区别?你会如何选择?
答:两者都是主流的分布式追踪系统,但设计理念不同。
维度
SkyWalking
Jaeger
语言
Java
Go
埋点方式
Java Agent自动埋点(无侵入)
SDK手动埋点 + 自动埋点
协议
自定义协议 + OTLP
OpenTracing/OTLP
存储
ES/H2/MySQL/BanyanDB
ES/Cassandra/Kafka
指标
内置Metrics分析(服务/实例/端点)
主要是Tracing,Metrics较弱
拓扑图
自动生成服务拓扑
需要额外配置
告警
内置告警引擎
需要外部告警系统
性能分析
支持代码级性能分析(Profile)
不支持
社区
Apache顶级项目,国内社区活跃
CNCF毕业项目,国际社区活跃
选择建议:
1 2 3 4 5 6 7 8 9 10 11 选SkyWalking: - Java技术栈为主 - 需要开箱即用的全套可观测性方案 - 需要无侵入的自动埋点 - 国内团队,中文文档和社区支持好 选Jaeger: - 多语言技术栈 - 已有Prometheus+Grafana的监控体系 - 需要与OpenTelemetry深度集成 - 偏好轻量级、可组合的方案
35. 🔴 如何实现跨线程、跨异步调用的Trace上下文传播?
答:异步场景下的上下文传播是分布式追踪的难点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 CompletableFuture.supplyAsync(() -> { return orderService.query(orderId); }); Span currentSpan = tracer.activeSpan();CompletableFuture.supplyAsync(() -> { try (Scope scope = tracer.activateSpan(currentSpan)) { return orderService.query(orderId); } }); ExecutorService tracedExecutor = new TracedExecutorService ( Executors.newFixedThreadPool(10 ), tracer); ExecutorService wrappedExecutor = Context.taskWrapping( Executors.newFixedThreadPool(10 )); producer.send(new ProducerRecord <>(topic, key, value, Collections.singletonList( new RecordHeader ("trace-id" , traceId.getBytes())))); String traceId = new String (record.headers().lastHeader("trace-id" ).value());Span span = tracer.buildSpan("consume" ).asChildOf(extractContext(traceId)).start();
36. 🔵 什么是OpenTelemetry?它和OpenTracing、OpenCensus的关系是什么?
答:OpenTelemetry(OTel)是可观测性领域的统一标准。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 历史: - OpenTracing:分布式追踪的API标准(CNCF项目) - OpenCensus:Google开源的Metrics+Tracing库 - 两者功能重叠,社区分裂 - 2019年合并为OpenTelemetry OpenTelemetry的定位: - 统一的可观测性数据采集标准 - 覆盖Traces、Metrics、Logs三大支柱 - 提供SDK、API、Collector、协议(OTLP) - 不提供存储和展示(交给Jaeger、Prometheus、Grafana等) 架构: Application(SDK/Auto-instrumentation) → OTLP协议 → OpenTelemetry Collector → Exporter → Jaeger/Prometheus/Elasticsearch/... 优势: - 厂商中立,避免锁定 - 一套SDK采集所有信号(Traces+Metrics+Logs) - 丰富的自动埋点库(HTTP、gRPC、JDBC、Redis等) - Collector支持数据处理(过滤、采样、转换、路由)
37. 🔴 如何利用可观测性数据实现自动根因分析(Root Cause Analysis)?
答:自动根因分析是可观测性的高级应用,能大幅缩短故障定位时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 方法论: 1. 异常检测(发现问题): - 基于统计的异常检测:3-sigma、MAD - 基于机器学习:时间序列异常检测(如Prophet、LSTM) - 多维度联合检测:同时监控延迟、错误率、流量 2. 关联分析(缩小范围): - 时间关联:在异常时间窗口内,哪些指标同时异常 - 拓扑关联:异常服务的上下游是否也异常 - 变更关联:异常时间点附近是否有发布、配置变更 3. 根因定位(精确定位): - 调用链分析:从异常Trace中找到耗时最长的Span - 对比分析:异常请求vs正常请求的Trace差异 - 下钻分析:从服务→接口→SQL→索引逐层下钻 实际案例: 告警:订单服务P99延迟从50ms飙到5s 自动分析: 1. 调用链分析 → 库存服务deductStock耗时4.8s 2. 库存服务指标 → 数据库连接池等待时间飙高 3. 数据库指标 → 活跃连接数打满,大量锁等待 4. 慢SQL分析 → 发现一个全表扫描的UPDATE 5. 变更关联 → 30分钟前有一次库存服务发布 6. 根因:新版本引入了一个缺少WHERE条件的UPDATE语句
38. 🔵 如何设计日志系统的架构?ELK和Loki各有什么优缺点?
答:日志系统是可观测性的基础设施。
日志架构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 应用层: - 结构化日志(JSON格式) - 包含TraceID、SpanID(与追踪系统关联) - 日志级别合理使用(ERROR/WARN/INFO/DEBUG) 采集层: - Filebeat/Fluentd/Vector:从文件采集 - 直接推送:应用直接发送到Kafka 传输层: - Kafka:缓冲和削峰 - 保证日志不丢失 处理层: - Logstash/Flink:日志解析、过滤、富化 - 提取关键字段、脱敏、添加标签 存储层: - 热数据:Elasticsearch/ClickHouse(最近7天) - 温数据:降低副本数(7-30天) - 冷数据:对象存储(>30天) 查询层: - Kibana/Grafana:可视化查询 - 支持全文搜索、字段过滤、时间范围
ELK vs Loki:
维度
ELK
Loki
索引方式
全文索引(倒排索引)
只索引标签,不索引日志内容
存储成本
高(索引占用大量存储)
低(压缩存储,无全文索引)
查询能力
强(支持复杂的全文搜索)
弱(只能按标签过滤+grep)
资源消耗
高(ES需要大量内存和CPU)
低(轻量级)
运维复杂度
高(ES集群运维复杂)
低(单二进制文件)
生态
成熟,插件丰富
Grafana生态,与Prometheus配合好
选择建议:
1 2 3 4 5 6 7 8 9 10 选ELK: - 需要复杂的全文搜索 - 日志分析是核心需求 - 有专门的运维团队 选Loki: - 已有Grafana+Prometheus体系 - 成本敏感 - 日志主要用于排障,不需要复杂分析 - 小团队,运维能力有限
39. 🔴 如何设计一个高效的告警系统?如何避免告警风暴和告警疲劳?
答:告警系统的核心挑战不是发告警,而是发”有用的”告警。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 告警分层: P0(致命):核心业务不可用 - 支付成功率<95% - 订单服务全部不可用 - 数据库主库宕机 → 电话 + 短信 + 钉钉,5分钟内响应 P1(严重):核心业务降级 - 接口P99>2s - 错误率>5% - 从库延迟>30s → 短信 + 钉钉,15分钟内响应 P2(警告):非核心功能异常 - CPU>80%持续5分钟 - 磁盘使用率>85% - 非核心服务错误率升高 → 钉钉,工作时间处理 P3(通知):需要关注但不紧急 - 证书即将过期 - 容量预警 → 邮件/工单
避免告警风暴:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 1. 告警聚合: - 同一服务的多个实例同时告警 → 聚合为一条 - 同一根因导致的级联告警 → 只发根因告警 - 时间窗口内的重复告警 → 合并 2. 告警抑制: - P0告警触发后,抑制该服务的P1/P2告警 - 已知维护窗口期间抑制告警 3. 告警收敛: - 持续告警不重复发送 - 首次告警 → 5分钟后未恢复再发 → 30分钟后再发 - 恢复后发送恢复通知 4. 动态阈值: - 不用固定阈值,用基于历史数据的动态基线 - 工作日和周末的基线不同 - 大促期间自动调整阈值
40. ⚫ 请设计一个完整的可观测性平台架构,支持万级微服务实例。
答:万级实例的可观测性平台需要考虑数据量、性能和成本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 数据量估算(万级实例): - Metrics:10,000实例 × 500指标 × 15秒采集 = 33万数据点/秒 - Traces:假设总QPS=100万,1%采样 = 1万Trace/秒 - Logs:10,000实例 × 100条/秒 = 100万条/秒 架构设计: ┌─────────────────────────────────────────────┐ │ 应用层 │ │ OTel SDK + Agent(自动埋点) │ └──────────────┬──────────────────────────────┘ │ OTLP ┌──────────────▼──────────────────────────────┐ │ OTel Collector集群 │ │ ├── 采样(Tail-based Sampling) │ │ ├── 过滤(去除健康检查等无用数据) │ │ ├── 富化(添加K8s元数据) │ │ └── 路由(按类型分发到不同后端) │ └──────┬──────────┬──────────┬────────────────┘ │ │ │ ┌──────▼───┐ ┌───▼────┐ ┌──▼──────────┐ │Prometheus│ │ Tempo │ │ Loki │ │(Metrics) │ │(Traces)│ │ (Logs) │ │ + Thanos │ │ │ │ │ └──────┬───┘ └───┬────┘ └──┬──────────┘ │ │ │ ┌──────▼─────────▼─────────▼──────────────────┐ │ Grafana │ │ ├── 统一查询(Metrics+Traces+Logs关联) │ │ ├── 告警引擎 │ │ ├── 服务拓扑图 │ │ └── SLO Dashboard │ └─────────────────────────────────────────────┘ 关键设计决策: 1. Thanos:Prometheus的长期存储和全局查询方案 2. Tempo:Grafana的追踪后端,支持对象存储,成本低 3. Loki:只索引标签,存储成本是ES的1/10 4. 统一用Grafana查询,Metrics→Traces→Logs一键跳转
五、网络与中间件故障排查(41-50题) 41. 🔵 线上服务出现大量Connection Timeout和Read Timeout,如何区分和排查?
答:Connection Timeout和Read Timeout是两种完全不同的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 Connection Timeout(连接超时): - TCP三次握手未在超时时间内完成 - 原因: a. 目标服务不可达(IP/端口错误、防火墙拦截) b. 目标服务连接队列满(backlog满了) c. 网络丢包严重 d. 目标服务进程挂了但端口还在监听(如K8s Pod正在终止) 排查: telnet target_host target_port # 测试连通性 ss -lnt | grep <port> # 查看目标端口监听状态和backlog netstat -s | grep -i "listen" # 查看SYN队列溢出 tcpdump -i eth0 host target_host and port target_port # 抓包分析 Read Timeout(读超时): - TCP连接已建立,但在超时时间内未收到响应数据 - 原因: a. 目标服务处理慢(慢SQL、锁等待、下游调用慢) b. 目标服务GC停顿 c. 网络延迟突增 d. 响应数据量大,传输时间长 排查: # 在目标服务上查看请求处理时间 # 检查目标服务的CPU、内存、GC状态 # 抓包查看响应时间 tcpdump -i eth0 -A host target_host and port target_port
42. 🔴 如何排查微服务之间的网络抖动问题?
答:网络抖动表现为延迟间歇性升高,是最难排查的问题之一。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 ping -i 0.1 target_host mtr target_host ifconfig eth0 ethtool -S eth0 netstat -s | grep -i retrans 1. 网络带宽打满: iftop -i eth0 2. TCP队列溢出: ss -lnt 3. 容器网络问题(K8s): iptables -L -n | wc -l cat /proc/sys/net/netfilter/nf_conntrack_count cat /proc/sys/net/netfilter/nf_conntrack_max 4. DNS解析慢: dig target_service.namespace.svc.cluster.local
43. 🔴 Redis突然变慢,如何排查?
答:Redis变慢的排查需要从多个维度入手。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 redis-cli --latency redis-cli --latency-history redis-cli SLOWLOG GET 10 redis-cli --bigkeys redis-cli INFO memory redis-cli INFO persistence
常见原因:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 1. 大Key操作: - HGETALL一个100万field的Hash - DEL一个包含100万元素的Set - 解决:拆分大Key,使用HSCAN/SSCAN分批操作 - Redis 4.0+:UNLINK异步删除 2. 持久化阻塞: - RDB fork子进程时,如果内存大(>10GB),fork耗时可能>1秒 - AOF重写同理 - 解决:控制实例内存大小(<10GB),使用SSD 3. 内存交换(Swap): - Redis使用了Swap,性能急剧下降 - 检查:cat /proc/<redis-pid>/smaps | grep Swap - 解决:确保物理内存充足,禁用Swap 4. 网络问题: - 客户端和Redis之间网络延迟 - 使用Pipeline减少网络往返 5. CPU绑定: - Redis是单线程,如果CPU被其他进程抢占 - 解决:taskset绑定CPU核心
44. 🔴 Kafka消费延迟(Consumer Lag)突然增大,如何排查和处理?
答:Consumer Lag是Kafka最常见的运维问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 kafka-consumer-groups.sh --bootstrap-server localhost:9092 \ --describe --group my_consumer_group kafka-run-class.sh kafka.tools.GetOffsetShell \ --broker-list localhost:9092 --topic my_topic --time -1
常见原因及处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 1. 消费者处理慢: - 消费逻辑中有慢操作(DB写入、RPC调用) - 排查:在消费逻辑中添加耗时日志 - 解决:优化消费逻辑、异步处理、批量操作 2. 消费者数量不足: - 分区数 > 消费者数,部分消费者处理多个分区 - 解决:增加消费者实例(不超过分区数) 3. 消费者Rebalance频繁: - 消费者频繁加入/退出导致Rebalance - Rebalance期间所有消费者暂停消费 - 排查:消费者日志中搜索"rebalance" - 解决: - 增大session.timeout.ms(默认10s→30s) - 增大max.poll.interval.ms(默认5min→10min) - 减小max.poll.records避免单次处理时间过长 4. 生产端突发流量: - 上游突然产生大量消息 - 解决:临时增加消费者、增加分区数 5. 消费者GC停顿: - GC导致消费者心跳超时,触发Rebalance - 解决:优化GC参数,增大session.timeout.ms
45. 🔵 如何排查微服务的级联故障(雪崩)?
答:级联故障是分布式系统中最危险的故障模式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 典型雪崩过程: 1. 数据库慢查询 → 连接池耗尽 2. 服务A调用数据库超时 → 线程池堆积 3. 服务B调用服务A超时 → 服务B的线程池也堆积 4. 服务C调用服务B超时 → 继续传播 5. 整个调用链全部不可用 排查方法: 1. 从告警时间线倒推: - 哪个服务最先告警? - 告警的传播路径是什么? - 最先告警的服务就是根因所在 2. 调用链分析: - 从异常Trace中找到最深层的慢Span - 通常是数据库、缓存、或外部服务 3. 指标关联: - 线程池使用率、连接池使用率 - 错误率、超时率 - 找到最先异常的指标 预防措施: 1. 超时设置:所有外部调用必须设置超时 2. 熔断器:Hystrix/Sentinel/Resilience4j 3. 限流:保护服务不被过载 4. 隔离:线程池隔离、信号量隔离 5. 降级:非核心功能自动降级
46. 🔴 线上出现TCP连接泄漏(CLOSE_WAIT堆积),如何排查和处理?
答:CLOSE_WAIT堆积是常见的连接泄漏问题,表示本端没有正确关闭连接。
1 2 3 4 5 6 7 8 9 10 11 12 13 ss -ant | awk '{print $1}' | sort | uniq -c | sort -rn netstat -ant | awk '{print $6}' | sort | uniq -c | sort -rn ss -antp state close-wait
常见原因:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 HttpURLConnection conn = (HttpURLConnection) url.openConnection();InputStream is = conn.getInputStream();Connection conn = dataSource.getConnection();PreparedStatement ps = conn.prepareStatement(sql);ResultSet rs = ps.executeQuery();Jedis jedis = jedisPool.getResource();jedis.get("key" ); httpClient.execute(request, new FutureCallback <HttpResponse>() { @Override public void completed (HttpResponse response) { EntityUtils.consume(response.getEntity()); } @Override public void failed (Exception ex) { } });
处理方案:
1 2 3 4 5 6 7 1. 代码修复:确保所有连接在finally/try-with-resources中关闭 2. 连接池泄漏检测: - HikariCP:leakDetectionThreshold=60000 - Jedis:设置maxWaitMillis,超时报错而非无限等待 3. 内核参数调优(临时缓解): # 减少CLOSE_WAIT的存活时间(不推荐,治标不治本) echo 60 > /proc/sys/net/ipv4/tcp_keepalive_time
47. 🔴 如何排查DNS解析导致的性能问题?
答:DNS问题在微服务架构中很常见但容易被忽视。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 dig @dns-server target-service +stats -Dsun.net.inetaddr.ttl=30 networkaddress.cache.ttl=30 networkaddress.cache.negative.ttl=10 kubectl top pod -n kube-system -l k8s-app=kube-dns
48. 🔵 服务发布后出现问题,如何快速回滚?回滚策略有哪些?
答:快速回滚能力是生产环境的生命线。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 回滚策略: 1. 代码回滚: - 回滚到上一个版本的代码/镜像 - K8s:kubectl rollout undo deployment/my-app - 最常用,最可靠 2. 配置回滚: - 如果是配置变更导致的问题 - 配置中心(如Apollo/Nacos)支持版本回滚 - 回滚后实时生效,无需重启 3. 数据库回滚: - 如果执行了DDL/DML变更 - 需要提前准备回滚SQL - 大表DDL回滚可能很慢,需要评估 4. 流量回滚: - 灰度发布场景,将流量切回旧版本 - Istio:调整VirtualService的权重 - Nginx:调整upstream权重 回滚决策流程: 1. 发现问题(监控告警/用户反馈) 2. 快速评估影响范围 3. 如果影响核心业务 → 立即回滚,事后分析 4. 如果影响非核心功能 → 评估是否可以热修复 5. 回滚后验证服务恢复正常 6. 事后复盘(Postmortem) 关键原则: - 回滚优先于修复(先止血再治病) - 回滚操作必须提前演练 - 每次发布都要有回滚方案 - 数据库变更必须可回滚
49. 🔴 如何排查Java应用的类加载问题(ClassNotFoundException、NoClassDefFoundError、ClassCastException)?
答:类加载问题在复杂的Java应用中很常见,尤其是使用了多个ClassLoader的场景。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 三种异常的区别: ClassNotFoundException: - 运行时动态加载类失败(Class.forName()、ClassLoader.loadClass()) - 类路径中确实没有这个类 - 排查:检查依赖是否正确引入 NoClassDefFoundError: - 编译时存在,运行时找不到 - 通常是依赖冲突导致(Maven/Gradle引入了错误版本) - 或者类的静态初始化块抛异常,导致类加载失败 ClassCastException: - 同一个类被不同的ClassLoader加载了两次 - 两个ClassLoader加载的同名类是不同的Class对象 - 常见于OSGi、Tomcat多应用、热部署场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 sc -d com.example.MyClass classloader -t mvn dependency:tree -Dincludes=com.google.guava -verbose:class -Xlog:class+load=info
50. ⚫ 请描述一次你经历过的最复杂的线上故障排查过程。
答:这是一个开放性问题,考察候选人的实战经验和系统化思维。
优秀回答的要素:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 1. 故障现象描述: - 什么时间发现的 - 影响范围多大 - 核心指标变化 2. 排查过程: - 第一反应是什么(先止血还是先排查) - 排查的思路和步骤 - 使用了哪些工具 - 走了哪些弯路 - 最终如何定位到根因 3. 根因分析: - 技术根因是什么 - 为什么之前没有发现 - 是否有多个因素叠加 4. 修复方案: - 短期修复(止血) - 长期修复(根治) - 如何验证修复有效 5. 复盘改进: - 监控告警是否及时 - 排查工具是否完善 - 流程是否需要改进 - 如何防止类似问题再次发生 示例框架: "某天凌晨2点收到告警,订单服务成功率从99.99%降到85%... 首先检查了最近的变更记录,发现2小时前有一次配置变更... 通过调用链追踪发现是库存服务超时... 进一步排查发现是数据库连接池泄漏... 根因是新引入的一个ORM框架在异常路径没有正确关闭连接... 临时修复:重启服务恢复连接池... 长期修复:修复代码 + 添加连接池泄漏检测 + 添加连接数监控告警..."
本模块共50题,覆盖CPU排查、内存泄漏、慢SQL分析、全链路追踪、可观测性设计、网络故障、中间件排障等核心主题。每道题都来源于真实的生产场景,能够有效考察架构师在高压环境下的系统化排障能力。