ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Spark任务从10分钟变5小时?四层排查法与真实案例复盘

Spark任务从10分钟变5小时?四层排查法与真实案例复盘 前阵子有个做数仓的同学在群里发了张截图同一个 Spark 任务代码提交记录完全没动过输入数据量也基本持平资源配额一模一样但有一次跑了 5 小时另一次同样的数据量只跑了不到 10 分钟。30 倍的差距直接把他整不会了。这类问题在面试里也经常被抛出来面试官一开口很多人的第一反应就是“是不是数据倾斜了”这个方向没错但回答得太单薄。30 倍的耗时差异很少是单一原因造成的往往是几个因素叠在一起才把任务时间拉爆。这篇文章我想把这类问题的完整排查思路讲透包括根因类型、实际排查步骤、一个我折腾过的真实案例以及面试时具体怎么组织答案。不管你是在线上面临真实性能问题还是正在准备面试都应该能从中拿到一些可以直接用的东西。1. 先别急着怀疑代码“同资源”这三个字本身就是疑点1.1 “资源配额相同”和“性能表现相同”是两码事很多人一听到“同资源”第一反应是“CPU 核数一样、内存一样、磁盘容量一样那环境就是一样的”这个理解在排查性能问题时非常危险。控制台上看到的配额相同只代表申请的资源上限一样不代表你的任务实际获得了一样的性能体验。第一层是 CPU。容器和虚拟机场景里最容易踩的就是 CPU steal time。当宿主机负载升高时超卖环境下你的 vCPU 会被频繁调度走任务实际拿到的 CPU 时间片就会变少。top 命令里那列%steal如果长期超过 10%基本可以怀疑是邻居在抢资源。我记得有个线上问题某个任务白天跑得特别慢晚上就恢复正常最后查下来是同一个宿主机上的另一个容器在白天做压测把物理 CPU 抢走了一大半。第二层是磁盘。同样是 100G 数据物理存放位置不同IO 性能会差非常多。机械硬盘不同磁道的寻道时间、读写带宽完全不一样云盘场景更复杂有按量计费的 IOPS 限流一旦 IOPS 配额耗尽磁盘吞吐会被强制降速。也就是说“同一份数据”在不同时间可能命中不同的物理磁盘位置IO 路径性能自然不同。第三层是网络。任务之间如果有 shuffle、数据拉取、跨节点同步网络 QoS 策略和实际可用带宽都会影响速度。分布式集群在执行大任务时如果某段网络链路的流量接近上限TCP 重传率升高数据搬运效率会断崖式下跌。这一层在外表看也是“同资源”但实际网速可能差了数倍。第四层是内存。这里说的不是容量而是访问性能。NUMA 架构下数据落在本地内存还是远端内存延迟差异很大透明大页THP在一些高并发场景下会造成偶发的严重延迟Page Cache 是否命中、数据是否已经被缓存在内存中也会极大影响读取速度。这些变量叠加在一起最终结果就是资源配额一样实际性能可以相差一个数量级。1.2 “同一份数据”也一样有冷热、有局部性“数据还是那份数据跑得慢一定不是数据的问题”这句话我也听过太多次了。其实“同数据”在分布式系统里有好几个容易被忽略的维度。首先是数据分片。一份大表如果分区字段的取值分布变了或者某些 key 的热度变了同样的 SQL 可能产生完全不同的执行计划。比如某个 join 的 key 在某段时间内突然出现了几个超大热点 key这几个 key 会把对应 task 拖成超长时间的长尾整体任务时间自然就被拉爆了。其次是数据缓存状态。同一份数据如果刚被另一个任务读过OS 的 PageCache 或计算引擎的缓冲池里就有缓存下次再读会非常快如果数据很久没被访问缓存早已失效重新读盘就要走真实的磁盘 IO。大数据场景尤其常见文件属性没变但块在 HDFS 或对象存储里的副本位置可能已经漂移缓存也早就被淘汰了。另外还有数据本地性。调度器如果能把任务调度到数据所在的节点读本地文件自然很快如果数据在远端节点任务就要通过网络拉数据速度会明显下降。某些调度策略在资源紧张时会默认把任务调度到非本地节点这种决策在同样数据量下就能让任务耗时相差好几倍。1.3 环境差异不是只有“环境变量”环境差异经常被缩小为一个概念“环境变量不同”。实际上比环境变量更隐蔽的是内核参数、JVM 参数、cgroup 限额、文件系统挂载选项这些看不见的配置差异。同样的 JVM 参数在不同内核版本或不同 CPU 型号的机器上JIT 编译策略、GC 行为都会有差异。内核参数方面透明大页开没开、swappiness 值是多少、文件系统挂载时有没有用 noatime这些都会在特定负载下造成明显的性能差异。cgroup 层面也有讲究CPU quota 比较低的时候容器会出现 CPU throttle明明 CPU 使用率不高但任务就是跑不快。我之前排查过一个任务同一个镜像在不同节点上跑耗时差了 3 倍最后发现是两台机器的透明大页配置不一样一个关了一个开了。这个隐藏很深如果不是看了内核参数很难想到是这里的问题。所以排查这类性能问题时一定要把“环境”这个词放宽环境变量只是最表层系统参数、调度策略、邻居负载都在环境范畴里。2. 耗时差30倍常见根因先分四层排查2.1 数据层分片不均、数据倾斜、冷热差异数据倾斜仍然是 30 倍差异里最常出现的“主犯”。不管是大数据的分布式计算还是单机处理里某些 key 大量集中只要有一小部分数据量特别大整个任务都会被它拖住。典型的特征任务里大部分 task 秒级完成极少数 task 需要几十分钟甚至几小时最终整体耗时被单个长尾 task 拉爆。这种情况数据总量没变但 key 的分布变了看起来就是“同代码同数据”耗时却完全不同。冷热数据是另外一个容易被忽略的点。同一批数据如果近期被高频访问OS 和引擎缓存都很热任务自然跑得快如果数据很久没被碰过缓存已经失效重新读盘当然慢。所以排查数据层问题时不要只看“数据大小没变”要确认数据在存储系统里的实际状态是刚被访问过还是已经被淘汰出缓存。我的经验是先看任务里的 task 耗时分布再看介质和缓存状态数据层问题至少能定位一半。2.2 资源层CPU 被借走、磁盘限流、网络抖动资源层问题的最大特点是“表面配额没变实际性能变了”。CPU 层要看 steal time 和 CPU throttle 次数磁盘层要看 IO 等待时间、await、util 和限流网络层要看重传率、丢包率和实际带宽。这三类问题在生产环境非常普遍而且经常被误判成代码问题。比如容器 CPU 配额用完以后被强制 throttleCPU 使用率看起来不高但任务时间被拉长了好几倍。再比如同一块云盘IOPS 耗尽之后被限流到很低的水平任务整体变慢但你没有注意监控面板上的 IOPS 尖峰。还比如跨机房的流量被网络策略限速shuffle 时间暴涨。判断资源层问题有一个高效抓手把任务挪到另一台物理机、另一个时段或者换一个网络路径跑一次如果很快恢复大概率是资源隔离层面的问题。2.3 运行层JVM GC、锁竞争、线程调度有时候数据没问题资源也充足但运行环境本身变了问题出在运行时。最常见的是 GC。任务的堆内存参数没变但内存里加载的对象模式变了或者老年代快要打满Full GC 次数变多运行时间就会从几分钟变成几十分钟。典型指标是 GC 停顿时间占总运行时间的比例这个比例一旦超过 10%就要怀疑 GC 问题。锁竞争和线程调度也值得关注。同样的代码当某个共享资源被多个线程争抢或者线程池大小配置不合理时线程会频繁进入阻塞、唤醒的状态任务被显著拖慢。另外也不要忽视物理机的 CPU 降频数据中心里降频现象其实比想象中常见一旦 CPU 频率降下来所有计算任务都会同步变慢而且监控上很难直接看到。排查运行层问题工具会换一套jstat、jstack、perf 是这里的主角。2.4 配置与依赖层参数差异、下游服务、代码版本“同样的代码”也可能是假象。很多线上任务用的其实是同一个分支但依赖的 jar 包版本、配置中心里的参数、数据库里的配置表可能在不同时间被悄悄改过。比如 SQL 走了不同的索引、某个功能开关被打开、连接池大小被调整这些都会影响任务耗时。下游服务不稳定更常见。任务跑的过程中如果依赖了一个数据库或外部接口而这个服务在某个时间点出现慢查询、连接池打满、重启或限流任务耗时就会被瞬间拉长。这类问题的特征是任务本身的计算量没变但等待外部响应的时间变长了。排查时优先检查任务中的等待时间、RPC 调用耗时和数据库慢查询日志往往很快就能发现元凶。为了便于记忆我把上面四层整理成了一个检查表。排查时从上到下快速过一轮能省很多时间排查层重点问题常用工具/指标数据层数据倾斜、分片不均、缓存冷热task 耗时分布、执行计划、PageCache 命中情况资源层CPU steal、磁盘限流、网络抖动top、iostat、sar、/proc/pressure/cpu运行层GC、锁竞争、线程调度、降频jstat、jstack、perf、CPU 频率配置与依赖层参数漂移、下游服务、版本差异配置中心对比、RPC 耗时、慢查询日志3. 五分钟定位法从现象到根因的排查实操3.1 第一步先复现多跑两次把现象变成数据看到任务变慢第一件事不是查代码而是先确认“慢”是不是稳定复现。很多业务同学会被一次抖动吓到结果重跑一次又快回来了那大概率是偶发性的资源竞争或网络抖动。所以第一步至少在同一环境里重跑两三次记录每次的耗时、资源使用曲线和关键事件时间点把“慢 30 倍”这个模糊描述转换为“哪一次运行、在哪段时间、哪一项指标异常”。复现阶段强烈建议开启时序监控。不需要太复杂重点是记录 CPU、内存、磁盘 IO、网络、GC 五类基础指标前后对比。如果恰好有历史监控系统能保留指标那就更省事直接对比两次运行的差异点往往一眼就能找到方向。我还习惯给每次任务打一个“运行环境标签”包括节点信息、邻居负载、时段这样后续回溯能省掉大量重复定位。3.2 第二步优先排除外部资源问题再做内部诊断我在实战中习惯用一个固定顺序外部环境优先内部运行时其次。因为外部问题往往能在几秒内检查完而内部问题定位成本更高。具体流程是先看 CPU steal 和 cgroup throttle再看磁盘 IO 和网络丢包最后看 GC 和线程状态。如果这些都没有异常再回到代码和数据层复查。下面是我常用的几条命令基本可以覆盖第一次全面筛查# 查看 CPU 使用率和 steal time top -d 2 # 查看容器 CPU 被限流情况 cat /sys/fs/cgroup/cpu.stat # 查看磁盘 IO 情况重点关注 await 和 util iostat -x 2 # 采集历史负载数据用于对比 sar -u -r -d 2 # 查看 GC 频率和停顿时间以 Java 任务为例 jstat -gcutil pid 1000注意cgroup cpu.stat 里的 nr_throttled 如果持续增长说明 CPU 被限流这时基本不用再看代码先把 CPU 配额和宿主机负载问题解决掉。3.3 第三步用“替代实验”锁定根因而不是靠猜当外部资源指标都正常就要做几组替代实验来缩小范围。比如换一台机器重跑这个任务如果变快说明节点环境有问题把任务的并发度调低或调高如果时间变化剧烈说明并发配置可能踩到了资源瓶颈把数据复制到本地重新跑一次如果变快说明远程读取或数据本地性有问题禁用透明大页或调整 JVM 堆大小如果变快说明内存层面有问题。替代实验的核心逻辑是“一次只动一个变量”。这是整个排查方法里最值钱的部分。因为真正复杂的性能问题很少能直接一眼看出来更多是靠几个实验把范围一点点压到最后一小块再做精准定位。面试时如果能把这一步讲清楚面试官基本就会认可你的排查能力因为他知道你不是在背题而是真在一线解决过问题。3.4 第四步把根因写入监控避免下次再踩找到根因之后请一定把对应的监控指标和告警加上。很多团队的问题在于这次修好了下次换个场景又踩一遍就是缺少沉淀。比如这次是 CPU steal 导致的那就给 CPU steal 指标加告警这次是 Full GC 时间过长那就把 GC 耗时周期加入监控这次是下游服务慢导致那就给 RPC 调用耗时设置阈值。每一项都沉淀成监控出现同类问题时就能第一时间感知而不是等用户投诉了才发现。4. 真实案例复盘Spark 任务从 10 分钟到 5 小时4.1 现象同一个任务隔一天就差了30倍我之前排查过一个很典型的场景一个 Spark SQL 任务每天凌晨定时跑。有一天突然从平时的 10 分钟左右涨到了接近 5 小时第二天又恢复正常。代码没有任何合入变更上游数据量也和前一天持平资源配额也没有变动。从表面看这就是教科书般的“同代码同数据同资源”。当时组里有人怀疑是数据倾斜立刻去查表的数据分布结果发现所有分区大小都挺均匀没有明显倾斜的 key。我当时没有急着再看数据而是先翻了监控。结果第一眼就发现了异常那天的任务运行期间CPU steal time 平均值超过了 20%峰值甚至到 40%。因为任务本身不是 CPU 密集型的CPU 使用率本来就不高所以大家一开始都忽略了这项指标。我进一步查了任务的运行节点发现当天它被调度到一个最近新加进来的宿主机上而那台机器上刚好还有一个高负载的在线服务容器。4.2 排查过程从 steal 到调度再到一次性解决顺着 steal 这个点往下查速度就很快了。我确认了当天运行节点上的其他容器负载发现邻居容器几乎把物理 CPU 抢光了导致我们的任务一直在等 CPU 时间片。再加上任务里本身有大量 shuffle 操作CPU 一慢shuffle 和后续 task 的启动都会连锁变慢最终把 10 分钟拖成了 5 小时。我后来做了两件事第一把任务固定调度到一台 CPU 资源更充足的节点上或者在资源配额上把 CPU 请求值调高避免被分配到高负载宿主机第二针对 Spark 任务的 shuffle 和数据本地性做了优化比如开启动态资源分配让任务运行时的并行度可以根据输入大小自动调整。优化之后这个任务不仅稳定回到 10 分钟波动幅度也明显变小。这个案例给我的印象很深30 倍的差距很多时候不是单点故障而是“资源竞争 任务特性”叠加的结果。4.3 复盘收获慢任务的锅往往不在“最新一次改动”复盘的时候大家一起做了个总结任务变慢的第一嫌疑不该是代码变更也不能只盯着数据倾斜。现场现象类似时先拉时序监控看系统层指标再往下排查才是更高效的路径。另外为了快速确认节点调度对任务的影响我们后来做任务性能对比时会额外记录“运行节点”和“邻居负载”的标签方便后续回溯。这个案例在面试里其实也很有用。如果你能自然地讲出一个这样的小故事并且把每一步排查动机说明白就比单纯背知识点要打动人得多。面试官想听的从来不是标准答案而是你解决问题的真实过程。5. 面试怎么回答一套能直接套用的表达框架5.1 面试官问这题到底想听什么这题背后考察的其实是几件事一是系统思维遇到问题会不会做概率排序而不是逮住一个方向硬挖二是工具功底能不能说出具体的排查命令和指标三是沟通表达很多候选人其实会排查但一开口像挤牙膏想到一个点说一个点没有条理四是经验沉淀如果候选人能顺带讲出一个真实处理过的案例会显著加分。所以你的回答不要开头就蒙一个“可能是数据倾斜”。要先搭一个逻辑框架让面试官感觉到你有一套成熟的排查流程而不是靠运气在猜。我在面试候选人的时候只要对方能说出“我先用指标确认是不是环境问题再做替换实验验证”这题基本上就过了。5.2 黄金框架现象确认 → 变量隔离 → 分层排查 → 根因验证给一个可以参考的回答框架分 30 秒浓缩版和 3 分钟展开版。30 秒浓缩版现象确认先确认慢是可复现的记录两次运行的时间和资源指标变量隔离确认“同代码、同数据、同资源”是真的完全一致特别是节点、时段、邻居负载分层排查按“资源层 → 运行层 → 代码层 → 数据层”的顺序逐层排查根因验证设计一到两个替换实验锁定唯一变量给出结论和后续改进。3 分钟展开版可以在第二步多讲细节。比如“同资源”如何不等于“同性能”需要看 steal time、cgroup throttle、磁盘限流第三步时展开讲替代实验比如换节点、调整并发、复制数据到本地最后可以把上面那个 Spark 案例讲出来作为佐证。这样整个回答既有方法论又有实操细节还有个人经验面试官想不给高分都难。5.3 加分项与避坑怎么说才能让面试官记住你几个实战心得如果能提到 CPU steal、cgroup throttle、PageCache、GC 停顿、数据本地性这些词会显得你对底层理解很深如果能现场口述一条jstat -gcutil或cat /sys/fs/cgroup/cpu.stat命令说明你确实不是纸上谈兵。但千万不要做这几件事第一只讲“可能原因”不给出“如何验证”面试官会认为你在猜第二上来就说“查代码”忽略了环境问题显得没有全局视角第三把答案说得太复杂一定要有一两句能概括结论的话比如“我会先用监控确认是否为资源竞争如果是再通过替换实验锁定”。简洁、有结构、有验证手段这才是这类问题最好的呈现方式。我自己做性能问题排查最大的体会是慢任务往往不是一个“奇技淫巧”的问题而是一个“可观测性”的问题。只要你把运行环境的指标抓全了把复现的过程做扎实30 倍甚至 100 倍的差距最终都能落到一个具体的指标异常上。如果你正在准备面试不妨把这套框架先背熟再找一个实际场景练一遍效果会比背一百个零散知识点都管用。下次再遇到这种“莫名其妙变慢”的问题先不要慌乱从资源竞争和数据分布开始查起大概率能找到答案。
返回列表