Spark任务卡在Running状态别慌手把手教你用top和jstack定位CPU打满的元凶凌晨三点手机突然震动起来——监控系统又报警了。揉着惺忪的睡眼打开Spark UI发现某个Stage的十几个Task已经持续Running状态超过两小时没有报错但进度条纹丝不动。这种静默卡死往往比直接报错更让人头疼就像在黑暗中摸索电路故障看不到火花却知道电流肯定在某处形成了短路。1. 现象诊断区分真假Running状态真正的Running状态应该伴随着资源消耗和数据处理进度。当发现Task长时间Running却无进展时首先要确认几个关键指标CPU利用率通过Spark Executor的监控页面查看是否出现单核或多核持续100%网络IO检查Shuffle读写是否卡在某个传输阶段磁盘IO观察是否有异常的磁盘等待时间内存使用确认是否因GC停顿导致假死提示真正的CPU打满通常表现为Executor进程的单个核心持续100%而内存问题往往伴随GC日志中的Full GC记录最近处理的一个案例中Spark UI显示所有Executor都处于活跃状态但Stage进度停滞在78%。通过YARN ResourceManager跳转到对应节点的监控页面发现其中一个Container的CPU使用率曲线异常# 快速查看节点CPU整体负载 $ mpstat -P ALL 1 5 Linux 5.4.0-104-generic (worker-03) 07/15/2023 _x86_64_ (16 CPU) 03:15:17 AM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle 03:15:18 AM all 12.42 0.00 1.24 0.12 0.00 0.25 0.00 0.00 0.00 85.97 03:15:18 AM 0 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.002. 进程级定位找出罪魁祸首Executor当确认某个节点存在CPU满载情况后需要具体定位到Spark的哪个Executor进程在消耗资源。通过YARN或Standalone集群的管理界面可以找到对应容器的进程ID也可以直接登录节点排查# 列出所有Java进程及其main class $ jps -ml 12345 org.apache.spark.executor.CoarseGrainedExecutorBackend --driver-url spark://... 67890 sun.tools.jps.Jps -ml # 监控特定进程的CPU使用 $ top -p 12345 PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND 12345 spark 20 0 15.729g 2.431g 34520 R 100.2 7.3 72:30.15 java关键指标解读%CPU超过100%表示使用了多核比如250%意味着2.5个核心满载TIME持续增长的CPU时间表明存在计算密集型操作RES稳定增长的内存可能暗示内存泄漏3. 线程级分析揪出问题线程找到高CPU的Java进程后需要深入其内部线程。Java线程的CPU使用情况需要通过特殊参数查看# 显示进程内各线程CPU占用 $ top -H -p 12345 PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND 45678 spark 20 0 15.729g 2.431g 34520 R 99.8 7.3 45:20.15 java 45679 spark 20 0 15.729g 2.431g 34520 S 0.0 7.3 0:00.12 java记录下高CPU线程的PID如45678然后将其转换为十六进制因为jstack输出使用十六进制# 十进制转十六进制 hex(45678) # 输出: 0xb26e4. 堆栈分析定位问题代码获取线程ID后通过jstack捕获线程堆栈并搜索对应的十六进制ID# 获取线程dump $ jstack 12345 thread_dump.log # 在dump文件中搜索问题线程 $ grep -A 30 nid0xb26e thread_dump.log Executor task launch worker-0 #123 daemon prio5 os_prio0 tid0x00007f8d3c0e8000 nid0xb26e runnable [0x00007f8d2d3fe000] java.lang.Thread.State: RUNNABLE at java.util.zip.Inflater.inflateBytes(Native Method) at java.util.zip.Inflater.inflate(Inflater.java:259) at java.util.zip.InflaterInputStream.read(InflaterInputStream.java:152) at com.example.ZlibUtils.decompress(ZlibUtils.java:87) at com.example.DataProcessor.process(DataProcessor.java:42)典型的问题模式包括Native方法调用如压缩/解压、加密等循环操作正则匹配、复杂计算等锁竞争BLOCKED状态的线程5. 解决方案与预防措施针对常见的CPU打满场景这里提供几个实用解决方案5.1 数据异常处理对于前文发现的zlib无限循环问题修复方案是在解压逻辑中添加安全阀public byte[] decompress(byte[] input) throws DataFormatException { Inflater inflater new Inflater(); inflater.setInput(input); ByteArrayOutputStream outputStream new ByteArrayOutputStream(input.length); byte[] buffer new byte[1024]; int maxIterations 1000; // 安全计数器 while (!inflater.finished() maxIterations-- 0) { int count inflater.inflate(buffer); if (count 0) { // 无数据产出时终止 break; } outputStream.write(buffer, 0, count); } inflater.end(); return outputStream.toByteArray(); }5.2 资源监控配置在spark-defaults.conf中添加监控参数spark.executor.extraJavaOptions-XX:PrintGCDetails -XX:PrintGCTimeStamps spark.metrics.conf.*.sink.jmx.classorg.apache.spark.metrics.sink.JmxSink spark.ui.prometheus.enabledtrue5.3 诊断工具包建议在集群节点预装以下工具工具安装命令用途arthascurl -O https://arthas.aliyun.com/arthas-boot.jarJava诊断工具async-profilergit clone https://github.com/jvm-profiling-tools/async-profiler低开销性能分析ncduapt-get install ncdu磁盘空间分析6. 进阶技巧自动化诊断方案对于频繁出现的问题可以建立自动化诊断流程# 示例自动捕获高CPU线程的脚本 import subprocess import re def diagnose_spark_task(pid): # 获取线程CPU使用 top_output subprocess.check_output(ftop -H -b -n 1 -p {pid}, shellTrue).decode() thread_lines [line.split() for line in top_output.split(\n)[7:] if line] for line in thread_lines: if float(line[8]) 95.0: # CPU超过95% tid int(line[0]) hex_tid hex(tid) # 获取线程栈 jstack_output subprocess.check_output(fjstack {pid}, shellTrue).decode() stack re.search(fnid{hex_tid[2:]}.*?\n\n, jstack_output, re.DOTALL) if stack: print(f高CPU线程 {tid}({hex_tid}):) print(stack.group(0))把这个脚本加入监控系统的告警动作中可以在出现CPU满载时自动捕获现场信息避免手动登录节点的时间延迟导致丢失关键上下文。