基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

线上任务发现个别 TaskManager 节点的 CPU 利用率长期保持在 100%,你会如何利用各种诊断工具抓取火焰图(FlameGraph)或线程 Dump 来分析热点代码?

知识点图片

在处理 Flink 线上任务中个别 TaskManager(TM)节点 CPU 长期 100% 的问题时,需要一套从定位抓取再到分析的标准排查方法论。

以下是完整的实战排查步骤,涵盖 Flink 原生功能、基础命令以及高阶诊断工具(如 Async-profiler、Arthas)。


第一阶段:精准定位异常进程与线程

在抓取信息之前,必须先明确是哪个进程、哪个线程在消耗 CPU。

  1. 定位异常 TaskManager 的 PID
    • 登录到 CPU 100% 的机器或 Pod。
    • 使用 jps -lps -ef | grep TaskManager 找到 TM 的进程 ID(假设 PID 为 12345)。
  2. 定位高 CPU 消耗的 OS 线程
    • 执行 top -H -p 12345
    • 观察 %CPU 列,找到占用 CPU 最高的几个线程 PID(假设为 12350)。
    • 将该十进制的线程 ID 转换为十六进制:printf "%x\n" 12350,得到 303e。这个十六进制值对应 Java 线程 Dump 中的 nid(Native ID)。

第二阶段:轻量级诊断 —— 抓取与分析 Thread Dump

如果机器还没有完全卡死,可以先用轻量级方式抓取线程快照。

1. 使用 Flink Web UI(最简单,但可能因 100% 响应慢)

  • 进入 Flink UI -> TaskManagers -> 选中问题节点 -> Thread Dump
  • 优点: 无需登录服务器。
  • 缺点: 只能看瞬时状态,无法统计累计耗时。

2. 使用 JDK 自带工具 (jstack)

  • 在服务器上执行:jstack -l 12345 > /tmp/tm_jstack.txt
  • 分析方法:
    打开 tm_jstack.txt,搜索刚才转换的十六进制 nid(如 nid=0x303e)。
  • 经验法则: 连续抓取 3-5 次(间隔 5 秒),如果该线程每次都停留在同一段代码上,那大概率这里就是死循环或热点代码。

3. 使用 Arthas 的 thread 命令(极力推荐)

如果环境允许使用 Arthas,这是最高效的轻量排查方式:

  • 启动 Arthas 挂载到 TM:java -jar arthas-boot.jar 12345
  • 执行命令:thread -n 3(直接打印出当前 CPU 占用率最高的前 3 个线程的堆栈)。
  • 优点: 一键直达病灶,自动关联高 CPU 线程和 Java 堆栈,无需手动算十六进制。

第三阶段:深度诊断 —— 抓取与分析火焰图(FlameGraph)

Thread Dump 只能看瞬时状态,如果热点代码在多个方法间快速跳转,必须使用火焰图来统计 CPU 周期分布。

1. Flink Web UI 原生火焰图 (Flink 1.13+)

  • 前提: 必须在 flink-conf.yaml 中开启配置 rest.flamegraph.enabled: true(默认关闭,因为有少许开销)。
  • 操作: Flink UI -> TaskManagers -> 选中节点 -> FlameGraph 页签。
  • 分析: 看“平顶山”(最宽的横条),横条越宽,说明采样期间该方法处于调用栈顶部的次数越多,即吃 CPU 越多。

2. 使用 async-profiler(工业级标准,开销极低)

如果 Flink 未开启原生火焰图,或者需要分析 Native 代码、GC 线程,推荐使用 async-profiler

  • 下载与运行:
    bash
    # 抓取 30 秒的 CPU 运行数据,生成 html 格式的火焰图
    ./profiler.sh -d 30 -f /tmp/cpu_flamegraph.html 12345
  • 优点: 基于 JVM TI 机制,不会受到 Safepoint 偏见(Safepoint bias)的影响,结果极其精准,且能抓到 JVM 底层(如 GC、JIT 编译)的 CPU 消耗。

3. 使用 Arthas 的 profiler 命令

Arthas 底层也集成了 async-profiler:

  • 启动 Arthas 后执行:profiler start --event cpu
  • 跑 30 秒后执行:profiler stop --format html
  • 下载生成的 HTML 文件在浏览器中打开即可。

第四阶段:热点代码分析指南(常见 Flink 100% CPU 场景)

拿到火焰图或线程栈后,如何解读?以下是 Flink 中常见的 CPU 飙升原因,请对号入座:

1. 用户代码层面的热点 (User Code)

  • 频繁的序列化/反序列化: 火焰图显示大量 JacksonGsonFastjson 方法。优化: 避免在算子中频繁解析大 JSON,尽量在 Source 端一次性解析并转换为 Flink 内部 RowData 或 POJO。
  • 复杂的正则表达式: 火焰图显示 java.util.regex.Pattern,可能是发生了正则灾难性回溯
  • 死循环或低效算法: 堆栈始终指向你的某段 UDF(如 MapFunction / ProcessFunction 内部的 while 循环或复杂的双重 for 循环)。
  • 集合类滥用: 比如针对非常大的 List 执行 contains() 操作(O(N) 复杂度),导致 CPU 跑满。应改用 HashSet

2. Flink 框架与状态层面的热点 (State & Framework)

  • Kryo 序列化退化: 火焰图显示 KryoSerializer 占用过宽。原因: Flink 无法推断你的数据类型,退化为低效的 Kryo 序列化。优化: 注册 PojoTypeInfo,或者自定义 TypeInformation。
  • RocksDB 序列化/反序列化开销: 如果是大量频繁的状态读写,序列化会占据主导。优化: 开启微批处理(MiniBatch),或者优化业务逻辑减少不必要的状态访问。

3. JVM 层面的热点 (GC 引起)

  • GC 线程吃满 CPU: top -H 发现占用 CPU 的是多个名为 GC Thread 的线程。线程堆栈或火焰图显示大量 ParallelScavengeG1 相关的 Native 方法。
  • 原因: 内存泄漏或堆内存不足,导致 JVM 陷入持续的 Full GC (GC 死亡螺旋)。虽然表现是 CPU 100%,但根本原因是内存问题
  • 后续动作: 此时不应该再看 CPU 火焰图,而是应该抓取 Heap Dump(jmap -dump:live,format=b,file=heap.bin 12345)使用 MAT 进行内存泄漏分析。

总结排查套路:

  1. 先看系统监控(确定是 User CPU 高还是 Sys CPU 高)。
  2. 如果怀疑是 GC 导致,看一眼 GC 日志或 Flink UI 的 JVM 内存曲线。如果是 GC 造成的 CPU 100%,转战内存排查
  3. 如果是纯计算导致的 CPU 高,使用 Arthas thread -n 3 看瞬时栈。
  4. 如果瞬时栈看不准,使用 Async-profiler 抓 30 秒生成火焰图
  5. 顺藤摸瓜,找到最宽的栈帧,定位到具体的 UDF 代码行,重构业务逻辑。
00:00
00:00