6.3 调优清单与实战手法


文档摘要

6.3 调优清单与实战手法 本节摘要:调优不是背参数,而是"计数器定位瓶颈——对号入座拧旋钮——数字验证收益"的闭环。本节先立三步诊断法,再按瓶颈类型给出五组旋钮清单(Combiner 与压缩、倾斜、小文件、Shuffle 内存、并行与推测),每组都标注前置判断条件与验证指标;随后完整推演加盐两阶段聚合——倾斜问题的标准解法;最后划出"该换引擎"的判断线,为第 7 章做引。 三步诊断法:先看数,再动手 作业慢的原因只有几个藏身之处:数据搬运太多(Shuffle 大)、干活不均(倾斜)、搬的次数太多(小文件与溢写轮数)、机器没用满(并行度低)、白等慢机器(无推测)。诊断路径收敛为三步: 第一步,看总账。

6.3 调优清单与实战手法

本节摘要:调优不是背参数,而是"计数器定位瓶颈——对号入座拧旋钮——数字验证收益"的闭环。本节先立三步诊断法,再按瓶颈类型给出五组旋钮清单(Combiner 与压缩、倾斜、小文件、Shuffle 内存、并行与推测),每组都标注前置判断条件与验证指标;随后完整推演加盐两阶段聚合——倾斜问题的标准解法;最后划出"该换引擎"的判断线,为第 7 章做引。

三步诊断法:先看数,再动手

作业慢的原因只有几个藏身之处:数据搬运太多(Shuffle 大)、干活不均(倾斜)、搬的次数太多(小文件与溢写轮数)、机器没用满(并行度低)、白等慢机器(无推测)。诊断路径收敛为三步:

第一步,看总账。作业计数器里三条曲线定方向:MAP_OUTPUT_RECORDSREDUCE_INPUT_RECORDS(中间量级)、SHUFFLE_BYTES(网络搬运量)、各 Reduce 任务的 REDUCE_INPUT_RECORDS 分布(均衡度)。

第二步,找最慢环节。作业页面上每个任务的耗时分解——map 的溢写轮数(SPILLED_RECORDS 与输出记录数之比大于 2 说明反复溢写)、reduce 的 copy 与 merge 耗时占比。

第三步,单旋钮验证。一次只改一个参数重跑,对比计数器与总时长;不回到这一步的调优都是玄学。

观察到 指向 去哪组
SHUFFLE_BYTES 以 TB 计 中间数据太大 第一组 压缩与预聚合
一个 Reduce 记录数是同侪百倍 倾斜 第二组 加盐
Map 任务数上千、每个几秒 小文件 第三组 合并与 Combine
SPILLED_RECORDS 远超输出 内存不足反复溢写 第四组 缓冲参数
作业尾部一台机器拖几十分钟 慢节点 第五组 并行与推测

第一组:先把搬运量砍下来

优先级最高的一组,因为收益直接乘在所有后续环节上:

开 Combiner。前提是聚合函数满足结合律且业务容忍(求和、计数、最值可以;求平均不可以——除非改发"和与个数"的复合值)。验证指标:COMBINE_INPUT_RECORDSCOMBINE_OUTPUT_RECORDS 之比即本地压缩率。

job.setCombinerClass(IntSumReducer.class);

开中间压缩。map 输出落盘与网络传输都吃它,snappy 是速度与压缩率的平衡点:

<property><name>mapreduce.map.output.compress</name><value>true</value></property> <property><name>mapreduce.map.output.compress.codec</name> <value>org.apache.hadoop.io.compress.SnappyCodec</value></property>

减字段。中间键值只带下游需要的字段,宽表进 Shuffle 是常见浪费——2.3 节的结论在此兑现:能在 map 端扔掉的列,别让它上车

第二组:倾斜的标准解法——加盐两阶段

4.2 节留下的悬案在此收束。设 60% 行的 city 为 NULL 或某热点值,哈希分区必然把这一坨塞给单个 Reduce。解法分两阶段:

第一阶段(打散):map 对热点键加随机后缀——原键 NULL 变 NULL_0 到 NULL_9 十个桶,热点被均匀摊到十个分区,reduce 对各桶分别聚出"局部和";

第二阶段(还原):小作业把 NULL_0 到 NULL_9 的十条局部和再聚一次,后缀剥掉,得到 NULL 的真实总和。

public static class SaltMapper extends Mapper<LongWritable, Text, Text, LongWritable> { private Random rnd = new Random(42); // 固定种子 保可复现 private Text outKey = new Text(); @Override protected void map(LongWritable k, Text line, Context context) throws IOException, InterruptedException { String[] c = line.toString().split(","); String city = c[1].isEmpty() ? "NULL" : c[1]; int salt = rnd.nextInt(10); outKey.set(city + "_" + salt); // 每行随机进桶 context.write(outKey, new LongWritable(Long.parseLong(c[2]))); } }

第二阶段的 map 把 _数字 后缀剥掉再聚:

public static class UnsaltMapper extends Mapper<LongWritable, Text, Text, LongWritable> { @Override protected void map(LongWritable k, Text line, Context context) throws IOException, InterruptedException { String[] c = line.toString().split("\t"); // 阶段一输出 键 制表 值 context.write(new Text(c[0].replaceAll("_\\d+$", "")), new LongWritable(Long.parseLong(c[1]))); } }

手工复算一遍正确性:设 NULL 组总额 60000、共 600 行,盐值随机均摊后每桶约 60 行;阶段一产出 NULL_0 约 5980、NULL_1 约 6120……十条局部和;阶段二求和 5980+6120+……,无论各桶如何分,总和恒等于 60000——加盐只改分组不改数值,加法交换律保底。适用条件也要记牢:只对可拆分的聚合(和、计数、最大)成立;求中位数、去重计数(需 HyperLogLog 类可合并结构)要另想办法。盐的桶数按热点占比定:热点占比 p、Reduce 数 R,桶数取约 p×R 的量级,让热点摊薄到与普通键同量级。

另一个轻量手法是两表 Join 时的维表放大:订单表热点键加 0 到 9 盐,users 表每行复制 10 份各带一个盐——两侧同样配对,reduce 里按盐匹配,热门键的回调被切成十次。

第三组:小文件与输入侧

上万个小文件各生一个 Map 任务、每个几秒就结束,调度开销反而大于计算:

根治在存储:入湖前用 SequenceFile 合并——把许多小文件打包成少数几个大块容器,键存文件名、值存内容,Map 数立刻从上万落到几十;

临时缓解:用 CombineFileInputFormat 让一个 Map 任务消费多个小分片;

输出侧配套:Reduce 数别盲目调大(每个多写一份 part 文件),并用 LazyOutputFormat 杜绝空文件(4.3 节):

job.setInputFormatClass(CombineFileInputFormat.class); LazyOutputFormat.setOutputFormatClass(job, TextOutputFormat.class);

判断是否需要这组:Map 平均运行时长低于 30 秒、任务数上千,两个条件同时成立就该治。

第四组:Shuffle 内存旋钮

SPILLED_RECORDS 超过输出记录两倍,说明缓冲区太小、反复溢写。两处可拧:

<property><name>mapreduce.task.io.sort.mb</name><value>256</value></property> <property><name>mapreduce.map.sort.spill.percent</name><value>0.90</value></property> <property><name>mapreduce.reduce.shuffle.parallelcopies</name><value>10</value></property> <property><name>mapreduce.task.io.sort.factor</name><value>25</value></property>

前三者分别管 Map 缓冲大小、溢写阈值、Reduce 拷贝并发;sort.factor 同时作用于 Map 端分段归并与 Reduce 端多路归并。拧这些的前提:任务 JVM 内存配额同步跟上,否则缓冲调大直接 OOM——mapreduce.map.java.opts 设为任务容器内存的八成左右是安全线。

第五组:并行度与推测执行

  • Reduce 数:按"每任务 1 到 5 GB 压缩数据"反推;宁可少而饱满,不要多而琐碎(4.2 节的账);
  • Map 侧推测:默认开,看 SPECULATIVE_TASKS_REDUCTIONS_SAVED 是否大于零判断本集群赚不赚;
  • Reduce 侧推测:满载集群或任务高度同质时关掉(5.3 节的两类关闭条件);
  • 重跑与失败容忍:毒数据场景放宽 mapreduce.map.failures.maxpercent,配合脏行计数器决定阈值(5.3 节)。

一张总账表

手法 前置判断 验证指标
Combiner 聚合可结合 组合器进出记录比
中间压缩 SHUFFLE_BYTES 大 同项下降比例
加盐两阶段 单键占比畸高 各 Reduce 记录数方差
小文件合并 Map 数上千且短 任务数与总时长
缓冲调大 溢写轮数多 SPILLED_RECORDS
惰性输出 空文件多 输出文件计数
推测开关 长尾任务 最慢任务与均值比

每行都是"条件——动作——验证"三件套,没有验证指标的调优不进清单

何时收手:换引擎的判断线

以下信号出现两条以上,继续调 MapReduce 多半是沉没成本:迭代计算(每轮作业落盘、五次搬运吃掉收益);交互式延迟要求(分钟级作业启动税无法消除);复杂多阶段管道(十几个串联作业,中间产物管理失控);流式需求(范式只有批没有流)。这些正是第 7 章要正面讨论的范式边界——知道旋钮拧到头在哪,与会拧旋钮同样重要。

本节自查

  1. 给"作业 40 分钟,一个 Reduce 占 35 分钟"的画像写出三步诊断的前两步各看什么数字;
  2. 推演加盐求最大值:盐桶后各桶最大值如何还原全局最大,求中位数为何不行;
  3. 维表放大十倍后 users 表的存储与 Join 网络各变化多少,何时得不偿失;
  4. 把 Combiner 用于"求平均工资",写出正确与错误两种 map 输出设计并解释;
  5. 列出你自己集群上值得关掉 Reduce 侧推测的判据。

三幕剧在此谢幕。下一章站到剧场外,问最后一个问题:这台机器的边界在哪,边界之外是谁的舞台。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U