山大软工实践hive(2)-解析输入(OPTree是什么)

2021SC@SDUSC

山大软工实践hive(2)-解析输入(OPTree是什么)

目标

我的分析任务如下图
请添加图片描述
可以看见,这一层输入为OP Tree,经过逻辑优化后输出也为OP Tree。所以我要先搞明白OP Tree是什么

分析输入

首先要了解我的任务上一步传入的输入Operator Tree是个什么东西,才能理解这一步干什么
根据其他博客内容,应该与类Operator(算子)有关。Operator是个抽象类,后面被如CommonJoinOperator继承
(org.apache.hadoop.hive.ql.exec.Operator)

下面的代码段的三个变量证明Operator类最终会构成一个有向无环图(DAG),每个节点有节点id,有记录它们的父节点与子节点的两个数组

......
protected List<Operator<? extends OperatorDesc>> childOperators;
protected List<Operator<? extends OperatorDesc>> parentOperators;
protected String operatorId;

......

public void initOperatorId() {
    this.operatorId = getName() + "_" + this.id;
  }

还有个初始化方法initOperatorId(),可以看到operatorId的结构,但实际没找到有调用。疑是在做查询计划(我的任务的上一步)把id分配好了
请添加图片描述
根据博客说法,在实际执行时,会调用initialize方法,然后process方法
在Operator类里process方法是接口,所以先看initialize方法
下面的initialize方法是初始化数组,并调用实际初始化方法

protected void initialize(Configuration hconf, ObjectInspector inputOI,
      int parentId) throws HiveException {
    ......
    
    if (parentId >= inputObjInspectors.length) {
      int newLength = inputObjInspectors.length * 2;
      while (parentId >= newLength) {
        newLength *= 2;
      }
      inputObjInspectors = Arrays.copyOf(inputObjInspectors, newLength);
    }
    inputObjInspectors[parentId] = inputOI;
    
      上面在给原数组扩容直到大小大于parentId,然后在数组parentId处赋予inputOI
   	  下面为实际初始化
   	  
    initialize(hconf, null);
  }

实际初始化方法会 最后会尝试初始化子节点,最后执行结束初始化方法

 public final void initialize(Configuration hconf, ObjectInspector[] inputOIs)
      throws HiveException {

    // String className = this.getClass().getName();

    this.done = false;
    this.runTimeNumRows = 0; // initializeOp can be overridden
    // Initializing data structures for vectorForward
    this.selected = new int[VectorizedRowBatch.DEFAULT_SIZE];
    if (state == State.INIT) {
      return;
    }

    

    if (inputOIs != null) {
      inputObjInspectors = inputOIs;
    }
    
    Serilizable 让对象可被序列化,反序列化(输入输出流,文件系统) transient标识让相应变量不被序列化
    这里的如childOperatorsArray就是transient变量,下面有几个transient对应的变量
    // while initializing so this need to be done here instead of constructor
    childOperatorsArray = new Operator[childOperators.size()];
    上面有个setchildOperator方法(parent也有),childOperator的输入估计从那来的? 
    
    for (int i = 0; i < childOperatorsArray.length; i++) {
      childOperatorsArray[i] = childOperators.get(i);
    } 深拷贝
    
    multiChildren = childOperatorsArray.length > 1;确认是否有多个子节点
    childOperatorsTag = new int[childOperatorsArray.length];
    
    下面的循环确认每个子节点的父节点的包括本节点
    for (int i = 0; i < childOperatorsArray.length; i++) {
      List<Operator<? extends OperatorDesc>> parentOperators =
          childOperatorsArray[i].getParentOperators();
      childOperatorsTag[i] = parentOperators.indexOf(this);
      }
	......
	
    
    outputObjInspector = inputObjInspectors[0];
    确认输出对象

    
	......
      initializeChildren(hconf);
  
	......
    // let's wait on the async ops before continuing
    completeInitialization(asyncInitOperations);
  }
protected void initializeChildren(Configuration hconf) throws HiveException {
    state = State.INIT;
    
    ......
    
    for (int i = 0; i < childOperatorsArray.length; i++) {
    子节点们也要执行initialize方法
      childOperatorsArray[i].initialize(hconf, outputObjInspector, childOperatorsTag[i]);
      if (reporter != null) {
        childOperatorsArray[i].setReporter(reporter);
      }
    }
    
  }
private void completeInitialization(Collection<Future<?>> fs) throws HiveException {
 
	......

    completeInitializationOp(os);接口方法
  }

这些代码表明有向无环图在这之前已被创建好,initialize方法只是在确认图的完整性、各种可能出现的问题、以及传递inputOI

看代码难以分析输入是什么,但幸运的是找到了宏观解析算子的博客

Operator是基本的抽象类,在此之上实现了各种算子如SEL(SelectOperator,用于执行投影,对应select),TS(TableScanOperator,每个对应一张表),FIL(对应where语句),LIM(对应limit),FS(对应数据输出),GBY(对应groupby)等等。这些算子对应HQL语句中各部分的操作以及输入输出。它们会构成DAG,代表如何运行

同时找到如SEL的process方法

@Override
  public void process(Object row, int tag) throws HiveException {
    ......
    int i = 0;
    try {
      for (; i < eval.length; ++i) {
        output[i] = eval[i].evaluate(row);
      }
    } 
    ......
    forward(output, outputObjInspector);
  }

对于个输入的行进行运算,然后foward方法把结果输出给下一个节点处理。不同类型的算子不同地实现process方法
但是在opeartor类里,initialize时并没有调用process,那么process在什么时候被调用呢?
根据查找,如MuxOperator,会调用子节点的process方法

@Override
  public void process(Object row, int tag) throws HiveException {
    ......
    int childrenDone = 0;
    for (int i = 0; i < childOperatorsArray.length; i++) {
      Operator<? extends OperatorDesc> child = childOperatorsArray[i];
      .......       
          child.process(handlers[tag].process(row), handlers[tag].getTag());
   	  .......   
    }
    ......
  }

之所以拿上面举例因为还发现了该类的注释,让我意识到逻辑优化干了什么----就是改变DAG让处理起来更快
请添加图片描述

再说回宏观结构。博主举了几个例子,比如下面HQL语句对应的算子的DAG的结构

select* from a where id>100 limit 10


上图的逻辑是明显的,第一个算子不断从表读取一条元组,然后给FIL节点筛选掉不需要的,然后再让SEL对过滤下来的元组做投影,再让LIM负责限制得到的条目数,达到数目后终止任务,FS负责把处理好的结果写入文件

然后一个复杂点的转换

from student a join score b on a.id=b.id
insert overwrite table d1 selecta.id,count(b.score) group by a.id
insert overwrite table d2 select a.id,b.scoreorder by b.score limit 10


上图由于涉及join以及两条insert,产生了一个复杂些的DAG
左半侧上下部分分别对student,score表的元组读取过滤,由于涉及到join,所以需要RS算子(处理数据分发,排序等问题)。对于join筛选出的数据,再分叉分别执行两个insert语句

这里面的问题是RS到底做了什么,为什么在后半段还会出现SEL->RS->SEL这种重复SEL的操作。根据源代码的注释,说的是’RS将输出发送到reduce阶段’。可以认为的是,RS负责把map阶段的数据传给正确的reduce,比如根据某种映射区分reduce任务,但仍不知SEL->RS->SEL干了什么

下一步做什么

目前的问题是,不同的算子的运行逻辑,比如RS算子。只有对这些有一定了解,才能理解逻辑优化的细节之处

参考

魔鬼_的博客

jiayuanv_127的博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值