Hive on Tez 中 Map 任务的数量计算
Hive on Tez Mapper 数量计算
在Hive 中执行一个query时,我们可以发现Hive 的执行引擎在使用 Tez 与 MR时,两者生成mapper数量差异较大。
主要原因在于 Tez 中对 inputSplit 做了 grouping 操作,将多个 inputSplit 组合成更少的 groups,然后为每个 group 生成一个 mapper 任务,而不是为每个inputSplit 生成一个mapper 任务。
下面我们通过日志分析一下这中间的整个过程。
1.MR模式
在 mr 模式下,生成的container数为116个:
对应日志条目为:
Input size for job job_1566964005095_0003 = 31733311148. Number of splits = 116
在MR中,使用的是Hadoop中的FileInputFormat,所以若是一个文件大于一个block的大小,则会切分为多个InputSplit;若是一个文件小于一个block大小,则为一个InputSplit。
在这个例子中,总文件个数为14个,每个均为2.1GB,一共29.4GB大小。生成的InputSplit数为116个,也就是说,每个block(这个场景下InputSplit 大小为一个block大小)的大小大约为256MB。
2.Tez模式
而在Tez模式下,生成的map任务为32个:
生成split groups的相关日志如下:
|mapred.FileInputFormat|: Total input files to process : 14
|io.HiveInputFormat|: number of splits 476
|tez.HiveSplitGenerator|: Number of input splits: 476. 3 available slots, 1.7 waves. Input format is: org.apache.hadoop.hive.ql.io.HiveInputFormat
...
|tez.SplitGrouper|: # Src groups for split generation: 2
|tez.SplitGrouper|: Estimated number of tasks: 5 for bucket 1
|grouper.TezSplitGrouper|: Grouping splits in Tez
|grouper.TezSplitGrouper|: Desired splits: 5 too small. Desired splitLength: 6346662229 Max splitLength: 1073741824 New desired splits: 30 Total length: 31733311148 Original splits: 476
|grouper.TezSplitGrouper|: Desired numSplits: 30 lengthPerGroup: 1057777038 numLocations: 1 numSplitsPerLocation: 476 numSplitsInGroup: 15 totalLength: 31733311148 numOriginalSplits: 476 . Grouping by length: true count: false nodeLocalOnly: false
|grouper.TezSplitGrouper|: Doing rack local after iteration: 32 splitsProcessed: 466 numFullGroupsInRound: 0 totalGroups: 31 lengthPerGroup: 793332736 numSplitsInGroup: 11
|grouper.TezSplitGrouper|: Number of splits desired: 30 created: 32 splitsProcessed: 476
|tez.SplitGrouper|: Original split count is 476 grouped split count is 32, for bucket: 1
|tez.HiveSplitGenerator|: Number of split groups: 32
Avaiable Slots
首先可以看到,需要处理的文件数为14,初始splits数目为476(即意味着在这个场景下,一个block的大小约为64MB)。对应日志条目如下:
|mapred.FileInputFormat|: Total input files to process : 14
|io.HiveInputFormat|: number of splits 476
获取到splits的个数为476个后,Driver开始计算可用的slots(container)数,这里计算得到3个slots,并打印了默认的waves值为1.7。
在此场景中,集群一共资源为 8 vcore,12G 内存,capacity-scheduler中指定的user limit factor 为0.5,也就是说:当前用户能使用的资源最多为 6G 内存。在Tez Driver中,申请的container 资源的单位为:
Default Resources=<memory:1536, vCores:1>
所以理论上可以申请到的container 数目为4(6G/1536MB = 4)个,而由于 Application Master 占用了一个container,所以最终available slots为3个。
在计算出了可用的slots为3个后,Tez 使用split-waves 乘数(由tez.grouping.split-waves指定,默认为1.7)指定“预估”的Map 任务数目为:3 × 1.7 = 5 个tasks。对应日志条目如下:
|tez.HiveSplitGenerator|: Number of input splits: 476. 3 available slots, 1.7 waves. Input format is: org.apache.hadoop.hive.ql.io.HiveInputFormat
…
|tez.SplitGrouper|: Estimated number of tasks: 5 for bucket 1
Grouping Input Splits
在Tez分配任务时,不会像mr那样为每个split生成一个map任务,而是会将多个split进行grouping,让map任务更高效地的完成。
首先Tez会根据计算得到的 estimated number of tasks = 5,将splits聚合为5个Split Group,生成5个mapper执行任务。
但是这里还需要考虑另一个值:lengthPerGroup。Tez会检查lengthPerGroup是否在 tez.grouping.min-size (默认为50MB)以及 tez.grouping.max-size(默认为1GB) 定义范围内。
如果超过了max-size,则指定lengthPerGroup为max-size,如果小于min-size,则指定lengthPerGroup为min-size。
在这个场景下,数据总大小为 31733311148 bytes(29.5GB左右,也是原数据大小),预估为5个Group, 则每个Group的 splitLength为6346662229 bytes(5.9GB 左右),超过了 Max splitLength = 1073741824 bytes( 1GB),所以重新按 splitLength = 1GB 来算,计算出所需的numSplits 数为 30 个,每个Split Group的大小为1GB。
在计算出了每个Split Group的大小为1GB后,由于原Split总数目为476,所以需要将这476个inputSplit进行grouping,使得每个Group的大小大约为1GB左右。
按此方法计算,预期的splits数目应为30个(但是仅是通过总数据大小/lengthPerGroup得出,尚未考虑inputSplits如何合并的问题,不一定为最终生成的map tasks数目)。
且最终可计算得出每个group中可以包含15个原split,也就是numSplitsInGroup = 15。相关日志条目如下:
|grouper.TezSplitGrouper|: Grouping splits in Tez
|grouper.TezSplitGrouper|: Desired splits: 5 too small. Desired splitLength: 6346662229 Max splitLength: 1073741824 New desired splits: 30 Total length: 31733311148 Original splits: 476
|grouper.TezSplitGrouper|: Desired numSplits: 30 lengthPerGroup: 1057777038 numLocations: 1 numSplitsPerLocation: 476 numSplitsInGroup: 15 totalLength: 31733311148 numOriginalSplits: 476 . Grouping by length: true count: false nodeLocalOnly: false
原splits总数目为 476,在对splits进行grouping时,每个group中将会包含15个inputSplits,所以最终可以计算出的group数目为 476/15 = 32 个,也就是最终生成的mapper数量。
|tez.SplitGrouper|: Original split count is 476 grouped split count is 32, for bucket: 1
|tez.HiveSplitGenerator|: Number of split groups: 32
所以在Tez中,inputSplit 数目虽然是476个,但是最终仅生成了32个map任务用于处理所有的 475个inputSplits,减少了过多mapper任务会带来的额外开销。
Split Waves
这里为什么要定义一个split waves值呢?使用此值之后会让Driver申请更多的container,比如此场景中本来仅有3个slots可用,但是会根据这个乘数再多申请2个container资源。但是这样做的原因是什么呢?
1.首先它可以让分配资源更灵活:比如集群之后添加了计算节点、其他任务完成后释放了资源等。所以即使刚开始会有部分map任务在等待资源,它们在后续也会很快被分配到资源执行任务
2. 将数据分配给更多的map任务可以提高并行度,减少每个map任务中处理的数据量,并缓解由于少部分map任务执行较慢,而导致的整体任务变慢的情况