挖挖Hive的代码(二)——生成MapReduce(上)

来源:互联网 发布:手机数据库查看器 编辑:程序博客网 时间:2024/05/16 00:26

Hive在做完语义分析后,会把查询语句的逻辑转化成一个由operator构成的DAG。但是这个DAG不能完全对应于Hadoop的计算框架,还需要根据Hadoop的框架要求,进一步的切割剪裁才行,就是封装成对应的Task对象。

切割这个DAG的逻辑在SemanticAnalyse.java中的genMapRedTasks方法里,核心代码如下:

    Map<Rule, NodeProcessor> opRules = new LinkedHashMap<Rule, NodeProcessor>();    opRules.put(new RuleRegExp(new String("R1"), "TS%"), new GenMRTableScan1());    opRules.put(new RuleRegExp(new String("R2"), "TS%.*RS%"),        new GenMRRedSink1());    opRules.put(new RuleRegExp(new String("R3"), "RS%.*RS%"),        new GenMRRedSink2());    opRules.put(new RuleRegExp(new String("R4"), "FS%"), new GenMRFileSink1());    opRules.put(new RuleRegExp(new String("R5"), "UNION%"), new GenMRUnion1());    opRules.put(new RuleRegExp(new String("R6"), "UNION%.*RS%"),        new GenMRRedSink3());    opRules.put(new RuleRegExp(new String("R6"), "MAPJOIN%.*RS%"),        new GenMRRedSink4());    opRules.put(new RuleRegExp(new String("R7"), "TS%.*MAPJOIN%"),        MapJoinFactory.getTableScanMapJoin());    opRules.put(new RuleRegExp(new String("R8"), "RS%.*MAPJOIN%"),        MapJoinFactory.getReduceSinkMapJoin());    opRules.put(new RuleRegExp(new String("R9"), "UNION%.*MAPJOIN%"),        MapJoinFactory.getUnionMapJoin());    opRules.put(new RuleRegExp(new String("R10"), "MAPJOIN%.*MAPJOIN%"),        MapJoinFactory.getMapJoinMapJoin());    opRules.put(new RuleRegExp(new String("R11"), "MAPJOIN%SEL%"),        MapJoinFactory.getMapJoin());

如上代码所示,每一个规则会对应一个处理程序,例如遍历程序发现满足R5规则时,就会触发GenMRUnion1类的实现来处理。下面具体说说各个规则都干了什么。

R1:遇到TS(TableScanOperator)的时候,会进入GenMRTableScan1类的逻辑。它的逻辑很简单,就是创建一个新的plan(MapredWork对象),并将其包成相应的Task。如果是当前query是一条analyze指令,则会紧跟着刚才创建的task,新建一个StatsWork,当然这个plan也会被包成对应的Task。

R2:遇到当前解析队列的头一个operator是TS,而当前是RS(例如:

TS(1)-SEL(2)-RS(3)
)这样的情形时,会调用GenMRRedSink1类的实现来处理。它的核心逻辑如下:

    if (opMapTask == null) {      if (currPlan.getReducer() == null) {        GenMapRedUtils.initPlan(op, ctx);      } else {        GenMapRedUtils.splitPlan(op, ctx);      }    } else {      GenMapRedUtils.joinPlan(op, null, opMapTask, ctx, -1, false, false, false);      currTask = opMapTask;      ctx.setCurrTask(currTask);    }

跟着if-else的逻辑来看:

如果当前RS的子operator没有绑定task,且当前task没有设置reducer的时候,那么会将这个RS的子operator设为当前task的reducer。

如果当前task(暂时称父task)已经有reducer了,那么不好意思,当前RS的子operator(reducer逻辑)只能放到一个新的task(暂时称子task)里去完成了,所以这个splitPlan方法会生成一个新的plan,并生成一个临时文件信息,通过这个临时文件将父task中的处理结果传递到子task中,由当前RS的子operator(新plan中的reducer)继续执行。这里还需要将当前RS(在父task中)替换为FS,而这个RS会被移到新创建的plan的map部分,当然它不能以top operator的形式出现,所以还会为它创建一个父operator——TS。例如遇到这样的DAG:

TS(1)-SEL(2)-GBY(3)-RS(4)-GBY(8)...     \SEL(5)-GBY(6)-RS(7)-GBY(9)...

那么会分割成两个task:

task1:                                            task2:    Map: TS(1)-SEL(2)-GBY(3)-RS(4)                    Map: TS(11)-RS(7)              \SEL(5)-GBY(6)-FS(10)    Red: GBY(8)...                                    Red: GBY(9)...

如果是join情况,当前RS的子operator可能已经有对应的task信息了(它已经在join的另一路中被遍历过了),所以此刻会将当前的plan与RS的子operator对应的plan相合并,并将当前plan及task对象抛弃。

R3:遇到当前解析队列出现两个RS时(例如:

TS(1)-SEL(2)-GBY(3)-RS(4)-GBY(5)-SEL(6)-RS(7)

),会调用GenMRRedSink2类的实现来处理。它的核心逻辑如下所示:

    if (opMapTask == null) {      GenMapRedUtils.splitPlan(op, ctx);    } else {      GenMapRedUtils.joinPlan(op, currTask, opMapTask, ctx, -1, true, false,          false);      currTask = opMapTask;      ctx.setCurrTask(currTask);    }

一段与R2规则类似的逻辑:

如果当前RS的子operator(reducer逻辑)没有对应的task信息,而当前RS已不是本次解析队列里的第一个遇到的RS了,此时可以断定当前plan对应的task必然已经有一个reducer了,所以当前这个RS的子operator必须变成一个新task的reducer。同R2规则里出现过的情况一样,调用GenMapRedUtils的splitPlan方法新建一个plan,通过临时文件作为媒介,将当前plan的处理结果传递到这个新建的plan中,用FS替换RS,转移RS到新的plan中,并创建相应的TS。

如果当前RS的子operator已经有对应的task信息了,joinPlan的逻辑会通过splitPlan方法为当前RS新建一个plan,并把这个新的plan与子operator对应的task合并为一个task。

R4:遍历operator遇到FS的时候,GenMRFileSink1的实现会通过该FS(FileSinkOperator)找到对应的MoveTask,并根据相关信息将MoveTask与当前Task关联起来。这个实现首先会判断当前FS是不是在做“insert overwrite”的操作,如果是,而且“hive.stats.autogather”开关设为了true,则会在FS对应的MoveTask后关联一个StatsTask(这个task是用来修改table或partition的metastore信息的,而MoveTask是用来处理数据的,分工不同)。接着,会通过“hive.merge.mapfiles”和“hive.merge.mapredfiles”开关来判断是否要在MoveTask之前添加合并数据的操作。合并数据的操作会添加一个MapRedTask来完成,至于这个task需不需要reducer就由hadoop的版本决定了:如果当前hadoop支持CombineHiveInputFormat(0.20以后是支持的),就不需要reducer了。完整的合并数据的操作是由一个关联了一个MoveTask和一个MapRedTask的ConditionalTask完成的,这个ConditionalTask会关联在FS所在的Task之后,而最初找到的MoveTask会分别关联到合并数据操作中新添加的MoveTask和MapRedTask之后。大致的关联关系如下:

                                 / MoveTask(1) -- MoveTask(0)MapRedTask -- ConditionalTask --     (FS)                      \ MapRedTask(merge) -- MoveTask(0)

R5:遍历operator遇到UNION时,会触发GenMRUnion1类的实现。这个实现的操作主要分两大类:Map-Only和Map-Reduce。

在Map-Only的情况下,通常只是简单的在交汇点UNION处切分开来,然后调整上下文信息,由其他规则去遍历解析:

TS(1)-SEL(2)-\                UNION(3)-SEL(4)-FS(5)    ==>   TS(1)-SEL(2)-UNION(3)-SEL(4)-FS(5)TS(6)-SEL(7)-/                                 TS(6)-SEL(7)-UNION(3)-SEL(4)-FS(5)

如果是Map-Reduce的境况,例如:

        TS(1)-SEL(2)-GBY(3)-RS(4)-GBY(5)-SEL(6)-\                                                 UNION(7)-SEL(8)-FS(9)  TS(10)-SEL(11)-GBY(12)-RS(13)-GBY(14)-SEL(15)-/

就得类似R3规则那样先拆分再合并了,结果会如下所示:

  ----  stage1--\           stage2  stage3--/  ----  stage1:    Map: TS(1)-SEL(2)-GBY(3)-RS(4)    Red: GBY(5)-SEL(6)-FS1  stage3:    Map: TS(10)-SEL(11)-GBY(12)-RS(13)    Red: GBY(14)-SEL(15)-FS2  stage2:    Map: UNION(7)-SEL(8)-FS(9)

R6:遍历operator发现当前RS跟着UNION,会触发GenMRRedSink3类的实现。如果当前UNION所在Task是一个Map-Only的Task,很简单,直接调用R2规则的实现来处理;否则UNION之前的operator串应该已经被R5规则处理过了,而这里的处理方式就又和R2类似了,只是不再调用initPlan而是initUnionPlan方法。示例如下:

  before:        TS(1)-SEL(2)-GBY(3)-RS(4)-GBY(5)-SEL(6)-\     TS(7)-SEL(8)-GBY(9)-RS(10)-GBY(11)-SEL(12)--UNION(19)-SEL(20)-GBY(21)-RS(22)-GBY(23)-SEL(24)-FS(25)  TS(13)-SEL(14)-GBY(15)-RS(16)-GBY(17)-SEL(18)-/
  after:             stage1-\  stage3--stage2  stage4-/  ----  stage1:    Map: TS(1)-SEL(2)-GBY(3)-RS(4)    Red: GBY(5)-SEL(6)-FS1  stage3:    Map: TS(7)-SEL(8)-GBY(9)-RS(10)    Red: GBY(11)-SEL(12)-FS2  stage4:    Map: TS(13)-SEL(14)-GBY(15)-RS(16)    Red: GBY(17)-SEL(18)-FS3  stage2:    Map: UNION(19)-SEL(20)-GBY(21)-RS(22)    Red: GBY(23)-SEL(24)-FS(25)

待续……

原创粉丝点击