资讯动态

Spark调优与故障排查面试实战:数据倾斜、Shuffle与OOM

发布时间:2026/10/27 4:17:38 来源:尧图企业网站定制
大数据岗位的面试里Spark 这一块几乎是绕不开的重头戏。卷一卷二聊过 RDD 的血缘、宽窄依赖、算子分类这些偏基础的东西到了卷三面试官的问法会明显往生产环境里到底怎么用这个方向偏——不再问你groupByKey和reduceByKey有什么区别这种课本题而是直接甩一个场景数据倾斜了你怎么查、怎么治Shuffle 写磁盘太慢你怎么调一个 Stage 卡在 99% 不动你从哪下手这类问题没有标准答案考的是你有没有真正在集群上被这些问题折磨过。我把卷三定位成调优与故障排查专场。它适合已经能写 Spark 作业、但一上生产就被各种 OOM、数据倾斜、任务长尾搞得焦头烂额的开发同学也适合准备跳槽、想把简历上熟悉 Spark 调优这句话真正撑起来的人。下面这些内容都是我在实际项目里反复验证过、也踩过坑的东西不是从文档里抄来的参数表。每个结论我都会说清楚为什么这么调因为面试官最烦的就是背参数——你说得出spark.sql.shuffle.partitions设成 200 是默认值但说不出你这个业务为什么该设成 800那等于没答。1. 先搞清楚面试官问调优时到底在考什么很多人一听到Spark 调优就开始背参数清单序列化用 Kryo、广播大表、调并行度、开堆外内存……背得滚瓜烂熟真被追问一句你为什么先调这个而不是那个就哑火了。问题出在没理解调优的本质Spark 作业慢绝大多数时候不是参数没调对而是数据分布和计算模型不匹配。参数只是最后那层微调方向错了参数调出花来也没用。1.1 调优的三个层次别一上来就拧参数我在实际排查里习惯把问题分成三层来看从上往下定位效率最高。第一层是业务与数据层。数据量到底多大、分区键选得合不合理、有没有可以提前过滤掉的数据、join 的两边是不是真的都需要全量参与。这一层能解决的问题往往占一半以上而且改造成本最低。比如一个明细表 join 维表维表只有几万行那根本不该走 shuffle join广播出去就完事了这一步做对后面所有参数调优都省了。第二层是计算模型层。算子的选择、Stage 的划分、shuffle 的规模、缓存的策略。同样是去重distinct会走一次全量 shuffle而如果业务允许近似用布隆过滤器或者先聚合再 distinct代价能差一个数量级。这一层考的是你对 Spark 执行计划的理解。第三层才是运行时参数层。并行度、内存比例、序列化方式、GC 策略。这一层见效快但天花板低而且很多参数是互相牵制的乱调容易按下葫芦浮起瓢。面试时如果你能主动说出我会先看数据分布和算子选择再看执行计划最后才动参数这个回答的层次感立刻就出来了。大部分人上来就说我会调并行度你一说三层定位差距就拉开了。1.2 一个反直觉的结论多数 OOM 不是内存不够新手遇到OutOfMemoryError的第一反应是加 executor 内存从 4G 加到 8G 加到 16G结果还是 OOM。为什么因为 Spark 的 OOM 分好几种加内存只对其中一种有效。真正的原因往往是这几种一是单分区数据量过大一个 task 要处理几百万条记录全塞进内存当然爆这时候该做的是提高并行度把分区切小而不是加内存二是笛卡尔积或者数据倾斜导致某个 task 的数据量是别人的几十倍加内存只是把爆炸时间往后推三是缓存策略不当把不该 cache 的中间结果 cache 了把宝贵的执行内存占满反而拖垮了 shuffle。还有一种隐蔽的是collect 到 driver数据量一大直接把 driver 撑爆这跟 executor 内存一点关系都没有。所以面试被问到 OOM正确的打开方式是先反问或者自述我会先确认是 driver 端还是 executor 端是堆内还是堆外是哪个 Stage 报的然后再判断是数据倾斜、分区过大还是缓存问题。这一句话就能让面试官知道你踩过真坑。1.3 面试官真正想听的调优故事长什么样我面过不少人也被人面过发现一个规律能打动面试官的从来不是我知道有哪些参数而是我遇到过一个具体问题我是这么一步步定位、这么验证、最后这么解决的。也就是一个完整的问题-定位-验证-复盘闭环。举个我自己的例子。之前有个作业每天凌晨跑平时 40 分钟某天突然跑了 3 个小时还没结束。我先看 Spark UI发现某个 Stage 有 200 个 task199 个几分钟就跑完了就剩 1 个卡着不动——典型的数据倾斜。然后我去看这个 task 处理的分区键发现是某个默认值比如空字符串或者 -1占了 80% 的数据。定位到根因后我做了两件事一是把倾斜的 key 单独抽出来加随机前缀打散二是给空值单独处理不参与 join。改完当天就恢复到 45 分钟。这个故事里没有一句参数但把看 UI、找长尾、查 key 分布、打散、验证整个链路讲清楚了面试官一听就知道你是真干过。所以准备面试的时候与其背参数不如把自己项目里两三个这样的故事捋顺讲的时候有细节、有数字、有对比比什么都管用。2. 数据倾斜从怎么发现到怎么治的完整链路数据倾斜是 Spark 面试的绝对高频题几乎每场必问。但很多人只会背加随机前缀这一招被追问你怎么知道倾斜了加前缀之后怎么保证结果正确就答不上来。这一节我把整条链路拆开讲从发现到治理每一步都说清楚。2.1 倾斜的典型信号UI 上的长尾 task数据倾斜最直观的表现就是同一个 Stage 内 task 耗时严重不均。打开 Spark UI 的 Stage 详情页看 task 的 Duration 分布如果大部分 task 几十秒个别 task 跑了几十分钟甚至小时级那基本可以确定是倾斜。这时候再看这个慢 task 的 Shuffle Read Size如果它读的数据量是其他 task 的几十上百倍实锤。还有一种信号是spill。慢 task 往往伴随大量磁盘 spill因为单分区数据太大内存放不下只能往磁盘写写磁盘又慢形成恶性循环。你在 UI 上看到某个 task 的 Spill (memory) 和 Spill (disk) 数值特别大也要警惕倾斜。我排查时习惯先按 Shuffle Read Size 倒序排一下 task 列表最大的那个如果明显离群就直接去查它处理的 key。这一步在 Spark UI 里点进 task 能看到它读的 block但更快的办法是单独跑一个countByKey或者抽样统计 key 的分布直接看哪个 key 的数据量异常大。2.2 根因分类不是所有倾斜都能用同一招倾斜的成因不同治法完全不同。我一般分成三类第一类是join 引起的倾斜也是最常见的。大表 join 小表本来该走广播但如果小表没被识别成小表比如统计信息缺失或者小表其实有几百 MB就会走 shuffle join然后大表里某个热点 key 全跑到一个分区。这类倾斜的典型特征是某个 key 在两张表里都大量存在。第二类是groupBy 或聚合引起的倾斜。某个维度值的数据量天然就大比如按省份聚合某个大省的数据量是其他省的好几倍。这类倾斜没法靠广播解决因为聚合本身就要 shuffle。第三类是分区键选择不当。比如用了一个分布极不均匀的字段做分区或者用了repartition(1)这种把数据全塞一个分区的操作。这类问题往往在代码 review 阶段就能发现。面试时如果你能主动把倾斜分类然后说不同类型的倾斜我用不同手段比笼统说我加随机前缀高到不知道哪里去了。2.3 治理手段对照表什么场景用什么招我把常用的治理手段整理成一张表方便对照。注意这些手段不是互斥的实际项目里经常组合使用。倾斜类型推荐手段适用前提代价与注意点大表 join 小表广播小表Broadcast Join小表能放进 executor 内存一般几百 MB 以内小表太大会撑爆内存需评估热点 key join热点 key 加随机前缀打散热点 key 数量有限、可枚举打散后要再聚合一次多一个 Stage空值或默认值倾斜空值过滤或单独处理空值不参与业务逻辑要确认过滤不影响结果正确性聚合倾斜两阶段聚合加前缀预聚合再汇总聚合操作可结合如 sum、count不适用于不可结合的聚合分区不均提高并行度或自定义分区器数据本身可均匀切分并行度不是越高越好受资源限制无法规避的极端倾斜单独抽出倾斜数据单独处理再 union倾斜数据可识别代码复杂度上升维护成本高这张表建议背下来面试时能对着场景说清楚用哪招、为什么、有什么代价基本就稳了。2.4 两阶段聚合的实操细节与验证方法两阶段聚合是聚合类倾斜最常用的解法但很多人只知道概念不知道具体怎么写、怎么验证。我展开说一下。假设你要按user_id做聚合但某些 user_id 数据量巨大。两阶段聚合的思路是第一阶段给 key 加随机前缀把一个大 key 拆成 N 个小 key 先做局部聚合第二阶段去掉前缀再做一次全局聚合。代码大概长这样import pyspark.sql.functions as F # 第一阶段加随机前缀局部聚合 salt_num 10 df_salted df.withColumn( salted_key, F.concat(F.col(user_id), F.lit(_), (F.rand() * salt_num).cast(int)) ) partial df_salted.groupBy(salted_key).agg(F.sum(amount).alias(partial_sum)) # 第二阶段去前缀全局聚合 final partial.withColumn( user_id, F.split(F.col(salted_key), _)[0] ).groupBy(user_id).agg(F.sum(partial_sum).alias(total_amount))这里有几个细节值得说。salt_num取多少合适一般取倾斜倍数的量级比如最热的 key 是平均的 50 倍那 salt 取 10 到 20 就能把单分区压力降下来取太大反而增加第二阶段负担。另外只有可结合的聚合才能这么干sum、count、max、min 可以但像collect_list、avg其实 avg 可以拆成 sum 和 count就要小心avg 得先拆成 sum 和 count 分别聚合再相除。验证方法也很关键。改完之后不能只看跑完了要对比改之前和改之后的结果是否一致。我一般会抽几个热点 key单独用原始数据算一遍结果跟两阶段聚合的结果比对确认数值完全一致才算通过。同时看 UI 上 task 耗时的分布长尾是否消失。这两步都做了才算真正解决问题面试时把验证方法说出来可信度立刻不一样。3. Shuffle 调优绕不开的性能大头如果说数据倾斜是局部爆炸那 Shuffle 就是全局开销。Spark 作业里但凡涉及宽依赖就有 Shuffle而 Shuffle 涉及磁盘 IO、网络传输、序列化反序列化是整个作业里最贵的环节。面试问 Shuffle 调优本质是问你懂不懂 Shuffle 的物理过程以及每个环节能怎么优化。3.1 Shuffle 到底贵在哪把物理过程拆开看很多人对 Shuffle 的理解停留在数据要重新分布但说不清具体发生了什么。我把 Shuffle 的过程拆成几个阶段来看你就知道钱花在哪了。Shuffle Write 阶段map 端的 task 把数据按分区键算 hash写到本地磁盘的临时文件里。这里涉及序列化把对象转成字节、排序如果是 sort-based shuffle、写磁盘落盘。Shuffle Read 阶段reduce 端的 task 通过网络把属于自己的那部分数据拉过来涉及网络传输、反序列化、可能的合并排序。中间还有一层是内存缓冲map 端写磁盘前会先在内存里攒一批攒满了再刷盘这个缓冲区大小直接影响写磁盘的频率。所以 Shuffle 的开销主要来自四块序列化反序列化的 CPU 开销、磁盘 IO、网络传输、以及内存缓冲不足导致的频繁刷盘。调优就是针对这四块下手。理解了这个你就能明白为什么用 Kryo 序列化调大 buffer size减少 shuffle 数据量这些手段有效——它们分别对应了上面四块开销。3.2 减少 Shuffle 数据量比调参数更根本的手段调参数是治标减少 Shuffle 数据量才是治本。我见过太多作业Shuffle 写了几个 T其实里面一大半是根本不需要参与计算的列。几个实操手段尽早过滤和裁剪列。在 shuffle 之前就把不需要的行过滤掉、不需要的列 select 掉。Spark 的 Catalyst 优化器虽然会做列裁剪但如果你在代码里先 join 再 selectjoin 的时候还是把整行数据 shuffle 了。正确的做法是先 select 出需要的列再 join。这一条听起来简单但实际项目里因为代码写得随意白白多 shuffle 几倍数据的情况非常普遍。能 map 端聚合的先聚合。reduceByKey和aggregateByKey会在 map 端先做一次局部聚合把数据量压下来再 shuffle而groupByKey不会它把所有数据原样 shuffle 过去。所以能用reduceByKey就别用groupByKey这是老生常谈但真到写代码时很多人图省事还是groupByKey。用广播代替 join。小表广播出去大表就不用 shuffle 了直接在每个 executor 本地做 map 端 join。这是减少 Shuffle 最有效的手段之一前提是小表真的小。考虑用 bucket 表。如果两张表经常按同一个 key join可以提前把两张表都按这个 key 分桶写成分区表join 的时候就能避免 shuffle。这个手段在数仓场景里很常用面试时提一嘴能加分。3.3 关键参数怎么定给出计算过程而不是背默认值面试官最烦的就是背默认值。你说spark.sql.shuffle.partitions默认 200这谁都知道但你能说出我这个作业 shuffle 数据量 200G每个分区目标 128M所以设成 1600这才叫会调。我一般这么估算并行度先看 shuffle 的总数据量除以单个分区期望处理的数据量经验值 100~200MB得到分区数。比如 200GB / 128MB ≈ 1600。但还要考虑 executor 的核数分区数最好是总核数的整数倍避免有的 executor 闲着有的忙死。假设集群有 50 个 executor每个 4 核总核数 200那 1600 正好是 200 的 8 倍比较合理。其他几个关键参数也说说我的取值逻辑spark.shuffle.file.buffermap 端写磁盘的缓冲区默认 32KB我一般调到 64KB 或 128KB减少刷盘次数。但别调太大会挤占执行内存。spark.reducer.maxSizeInFlightreduce 端每次拉取的数据量默认 48MB网络好的集群可以调到 96MB减少网络请求次数。spark.shuffle.io.maxRetries和spark.shuffle.io.retryWait网络抖动时的重试默认 3 次、5 秒集群网络不稳时可以适当加大避免因为偶发超时导致 task 失败重跑。spark.memory.fraction执行内存和存储内存的比例默认 0.6如果作业 shuffle 密集、缓存少可以适当调高给执行内存。这些参数没有放之四海皆准的值关键是你要能说出我基于什么观察、做了什么假设、调完看什么指标验证。面试时把这条逻辑讲清楚比报一堆数字有用得多。3.4 序列化Kryo 为什么快什么时候别用Kryo 序列化几乎是 Spark 调优的标配但很多人只知道Kryo 比 Java 序列化快说不清为什么。简单说Java 原生序列化会把类的元信息、字段名这些也写进去体积大、速度慢Kryo 不做这些它靠提前注册类来减少元信息开销序列化出来的字节更紧凑反序列化也更快。数据量越大这个差距越明显。用 Kryo 要注意两点。一是要注册自定义类不注册也能用但会退化成写全类名效果打折。二是不是所有场景都适合如果你的数据里大量是字符串Kryo 的优势没那么明显因为字符串本身序列化开销就那样。另外Kryo 对某些复杂嵌套结构的支持不如 Java 原生稳遇到序列化报错时先怀疑是不是某个类没注册或者结构太特殊。配置上spark.serializer设成org.apache.spark.serializer.KryoSerializer然后通过registerKryoClasses注册你用到的自定义类。这一步在 Scala 里很自然PySpark 里因为跨语言收益没那么大但 RDD 层面的操作还是能受益。4. 内存管理与 OOM 排查把账算清楚内存问题是 Spark 面试里最能区分用过和用好的题目。会写作业的人很多能把内存账算清楚、遇到 OOM 能一步步定位的人不多。这一节我把 Spark 的内存模型和排查方法讲透。4.1 统一内存管理模型执行内存和存储内存的博弈Spark 1.6 之后用的是统一内存管理模型把 executor 的堆内内存分成几块预留内存默认 300MB给系统用、用户内存剩下的 40%给用户代码和数据结构用、Spark 内存剩下的 60%也就是spark.memory.fraction控制的这部分。Spark 内存内部又分执行内存和存储内存两者共享一个池子可以互相借用。这个互相借用是理解很多现象的关键。执行内存用于 shuffle、join、sort、aggregation 这些计算过程存储内存用于 cache 和广播变量。当执行内存不够时它可以驱逐存储内存里的缓存数据如果缓存用的是 MEMORY_ONLY 级别被驱逐的数据就得重算反过来存储内存不够时也能借用执行内存的空闲部分。但如果执行内存被占满且无法驱逐就会 spill 到磁盘spill 多了性能就崩。所以调spark.memory.fraction和spark.memory.storageFraction的本质是在计算和缓存之间做权衡。缓存多的作业比如反复用同一份数据做迭代计算适当提高存储比例shuffle 密集的作业提高执行比例。没有标准答案看你的作业画像。4.2 executor 内存的分配账一个具体的计算例子面试经常问你怎么定 executor 的内存和核数这题考的是你会不会算账。我给一个我常用的估算流程。假设数据量 500GB集群单节点 128GB 内存、32 核。我一般这么配每个节点起 4 到 5 个 executor每个 executor 分配 20~24GB 内存、5~6 核。为什么这么分因为单个 executor 内存太大GC 压力会很大一次 full GC 停顿可能好几秒核数太多单个 executor 并发 task 多内存竞争激烈。经验上单 executor 内存控制在 20GB 上下、核数 5 左右比较平衡。具体到内存内部假设 executor 内存 20GB减去 300MB 预留剩约 19.7GB乘以 0.6 得到约 11.8GB 给 Spark 内存再乘以 storageFraction默认 0.5得到约 5.9GB 是存储内存的初始额度剩下的给执行内存。这个账算出来你就能判断我这个作业要 cache 5GB 的数据存储内存够不够。还有一个常被忽略的点是堆外内存。开启spark.memory.offHeap.enabled并把spark.memory.offHeap.size设成几个 G可以把一部分内存放到堆外减少 GC 压力。但堆外内存不受 JVM 管理用不好容易出更难查的问题我一般只在 GC 问题确实严重时才开。4.3 OOM 排查的完整链路从报错信息倒推根因真遇到 OOM别慌按链路一步步来。第一步看报错在 driver 还是 executor。driver 端 OOM 通常是 collect、广播大变量、或者 driver 端聚合导致的executor 端 OOM 通常是单 task 数据量过大、缓存过多、或者 shuffle 内存不足。第二步看是堆内还是堆外。报错信息里带java.lang.OutOfMemoryError: Java heap space是堆内带Direct buffer memory是堆外。堆内 OOM 加内存或者调内存比例堆外 OOM 调spark.memory.offHeap.size或者检查是不是 Netty 传输占太多。第三步看是哪个 Stage、哪个 task。Spark UI 上找到失败的 Stage看它的 task 分布如果又是长尾那就是倾斜如果所有 task 都 OOM那是整体内存不足或者分区太大。第四步看是不是缓存问题。检查代码里有没有cache()了不该 cache 的大表或者 cache 级别选得不对MEMORY_ONLY 在内存不足时会反复重算反而更慢可以考虑 MEMORY_AND_DISK。我踩过最坑的一次 OOM是 driver 端报的查了半天 executor 内存最后发现是代码里对一个几千万行的 DataFrame 做了collect()转成 list 再处理。这种问题加多少 executor 内存都没用得改成分布式处理。所以排查时先确认 OOM 发生在哪一端能省掉大量无用功。4.4 GC 调优什么时候该动什么时候别碰GC 调优是很多人的知识盲区一提到就头大。我的建议是大部分作业不需要动 GC 参数只有当你在 Spark UI 的 Executor 页面看到 GC Time 占比很高比如超过 10%或者日志里频繁出现 full GC 时才值得动手。真要调先看是哪种 GC 问题。如果是 young GC 频繁但每次很快通常是对象创建太频繁可以考虑复用对象、减少临时变量如果是 full GC 停顿长通常是老年代对象太多要么是缓存太大要么是有大对象长期存活。对应的手段包括调大新生代比例-XX:NewRatio、用 G1 收集器-XX:UseG1GC、调整spark.memory.fraction减少缓存占用。但我要强调GC 调优是最后手段。绝大多数GC 慢的问题根子还是在数据分布和算子选择上。你先把倾斜治了、把 shuffle 数据量降了GC 问题往往自己就消失了。面试时如果你能说出我一般先排查数据层面GC 参数是最后才考虑的反而显得你懂轻重。5. 执行计划与 Stage 划分看懂 Spark 的体检报告前面讲的倾斜、shuffle、内存都是症状而执行计划是体检报告。能看懂执行计划的人在问题发生之前就能预判。面试里如果被问到你怎么优化一个慢查询你能说我先 explain 看执行计划这个起手式就很专业。5.1 explain 输出怎么读从下往上看df.explain()或者df.explain(True)输出的执行计划很多人一看就晕。我的读法是从下往上、从右往左看因为数据是从底层的扫描一路往上经过各种算子处理到最终结果的。重点看几个东西Scan 节点看有没有做分区裁剪和列裁剪如果扫描了全表全列说明过滤条件下推没生效Join 节点看是 BroadcastHashJoin 还是 SortMergeJoin前者说明小表被广播了后者说明走了 shuffleExchange 节点就是 shuffle 的标志看到 Exchange 就要警惕数一数有几个每个都是一次数据重分布Aggregate 节点看是 HashAggregatemap 端预聚合还是普通的聚合。explain(True)会输出四个部分Parsed Logical Plan解析后的逻辑计划、Analyzed Logical Plan分析后的带上了表和列的解析、Optimized Logical Plan优化后的Catalyst 做了各种规则优化、Physical Plan物理计划最终执行的。我一般重点看 Optimized 和 Physical 这两个前者能看出优化器做了什么后者能看出实际怎么执行。5.2 Broadcast Join 没生效的几种常见原因广播 join 是优化利器但经常出现我明明写了广播提示怎么还是走了 SortMergeJoin的情况。原因通常有这几个一是小表其实不小。spark.sql.autoBroadcastJoinThreshold默认 10MB超过这个大小就不会自动广播。你可以手动调大这个阈值或者用broadcast()函数强制广播但前提是小表真的能放进内存。二是统计信息缺失。如果表没有做ANALYZE TABLE收集统计信息优化器不知道表有多大可能误判。这种情况手动 broadcast 提示能解决。三是join 类型不支持。比如 full outer join 就没法用广播 join因为两边都可能有不匹配的行。这种情况只能接受 shuffle。四是广播的表本身是个复杂子查询。如果 broadcast 里面套了一个需要 shuffle 的子查询那广播也救不了得先把子查询物化。排查的时候先看 explain 里 join 节点到底是什么类型再对照上面几条找原因。面试时能说出这几条说明你不是只会写broadcast()三个字。5.3 Stage 划分与 task 数量的关系Stage 的划分依据是宽依赖也就是 shuffle。每遇到一个 shuffle就切一个新 Stage。所以 Stage 数量大致等于 shuffle 次数加一。理解这个你就能预判一个作业大概有几个 Stage哪个 Stage 最重。task 数量则取决于每个 Stage 的分区数。map 端的 task 数一般等于输入数据的分区数比如 HDFS block 数reduce 端的 task 数由spark.sql.shuffle.partitions或者repartition决定。这里有个常见的坑reduce 端分区数设得太少导致单个 task 处理数据量过大既慢又容易 OOM设得太多task 调度开销和输出小文件问题又上来了。所以前面说的按数据量估算分区数是实打实要算的。还有一个细节是推测执行speculative execution。当某个 task 跑得特别慢时Spark 会在另一个 executor 上启动一个副本同时跑谁先完成用谁的结果。这个机制对长尾 task 有一定缓解作用但如果是数据倾斜导致的长尾推测执行只是治标副本照样慢。所以看到长尾先判断是资源问题还是倾斜问题别盲目开推测执行。6. 面试现场怎么把调优经验讲成加分项技术点讲完了最后说说面试表达。同样一套知识有人讲出来平平无奇有人讲出来让面试官频频点头差别就在表达方式。这一节是我自己面试和复盘总结的一些心得。6.1 用 STAR 结构讲项目但别讲成流水账讲调优经历STARSituation、Task、Action、Result是个好框架但很多人讲成了流水账背景说五分钟行动说一句结果忘了说。我的建议是压缩背景、突出行动和结果。背景一句话带过有个日更作业平时 40 分钟某天跑了 3 小时。任务一句话我要定位并解决这个性能退化。行动是重点要讲清楚你看了什么指标、发现了什么、做了什么假设、怎么验证的。结果要有数字优化后恢复到 45 分钟长尾 task 消失shuffle 数据量从 800G 降到 300G。面试官最想听的是行动部分因为那里面藏着你的排查思路和判断力。背景和结果都是陪衬。所以准备的时候把每个故事的 action 部分打磨到能讲两三分钟细节丰富、逻辑清晰比什么都强。6.2 主动暴露权衡比假装什么都懂更加分面试时最忌讳的是什么都懂。被问到一个你没处理过的场景硬编一个答案很容易被追问穿帮。更好的做法是主动说出你的权衡和边界。比如被问到两阶段聚合有什么缺点你可以说它多了一个 Stage代码复杂度上升而且只适用于可结合的聚合。如果倾斜的 key 无法枚举或者聚合逻辑不可结合我会考虑别的方案比如单独抽出倾斜数据用广播处理再 union 回去。这种回答既展示了你知道方案的局限又展示了你有备选方案比单纯说两阶段聚合很好用强太多。再比如被问到你怎么定并行度你可以说我按数据量估算但也会受集群资源限制。如果集群核数不够分区数设太大反而调度不过来这时候我会优先保证每个 task 的数据量在合理范围接受一定的资源等待。承认约束、给出取舍这才是真实工程的样子。6.3 几个高频追问的应对思路最后列几个我遇到过的高频追问以及我的应对思路。你调优之后怎么证明有效这题考的是验证意识。我的回答是看三个指标作业总耗时、Stage 内 task 耗时的方差方差小说明倾斜缓解、shuffle 读写数据量。三个指标都改善才算真优化不能只看总耗时因为总耗时可能被其他因素干扰。如果调优后反而变慢了怎么办这题考的是回滚意识。我的回答是任何调优都要有 baseline改之前记录指标改之后对比变慢就回滚。参数调优尤其如此很多参数是互相影响的单看一个参数变好不代表整体变好。你怎么在开发阶段就避免这些问题这题考的是工程素养。我的回答是代码 review 时重点看有没有groupByKey、有没有对大表collect、join 前有没有裁剪列、分区键选得合不合理。把这些检查项固化到团队的 review checklist 里比事后调优省事得多。Spark 3.x 相比 2.x 在调优上有什么变化这题考的是知识更新。可以提自适应查询执行AQE它能根据运行时统计信息动态调整 shuffle 分区数、自动处理倾斜 join、动态切换 join 策略。AQE 开启后很多以前要手动调的参数比如 shuffle 分区数可以交给它自动决定但也不是万能的极端倾斜还是得手动处理。能聊到这个层面说明你跟得上版本演进。说到底Spark 调优这件事参数是皮毛数据分布和计算模型是骨肉排查思路和验证意识是灵魂。面试官想找的不是一个会背参数表的人而是一个遇到问题能冷静定位、有章法地解决、并且知道方案边界的人。把上面这些内容吃透再结合自己项目里真实踩过的坑讲出来卷三这一关基本就稳了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑