Kylin--任务生成(源码解析)

943 阅读4分钟

kylin源码解读第一篇 job任务生成的过程 注意这个是job任务生成的过程,并不是build的过程。

一般kylin生成一个build的任务是在前端页面会发起一个build的任务或者refresh的任务。在cube的tab里有对应的按钮。

这个按钮会触发cubeController的build方法,然后会走 rebuild(cubeName, req); 这个方案。

然后会调用触发阶段刷新任务的方法:

buildInternal(
    cubeName, 
    new TSRange(
        req.getStartTime(), 
        req.getEndTime()
    ), 
    null,
    null,
    null,
    req.getBuildType(), 
    req.isForce() || req.isForceMergeEmptySegment()
    );

在上边的方法内部会调用下边这个方法:

jobService.submitJob(
    cube, 
    tsRange, 
    segRange, 
    sourcePartitionOffsetStart, 
    sourcePartitionOffsetEnd,
    CubeBuildTypeEnum.valueOf(buildType), 
    force, 
    submitter
);

然后这就是一个专门的jobService,注意kylin里这样的设计方式有很多。比如query也是这样的方式,有专门的queryService。 上边的方法会调用:

submitJobInternal(
    cube, 
    tsRange, 
    segRange, 
    sourcePartitionOffsetStart,
    sourcePartitionOffsetEnd, 
    buildType, 
    force, 
    submitter
);

在这一步会做一些检查,比如cube的签名,是否允许进行这个build操作,然后根据不同的build操作生成不同的任务然后根据数据源的分区,确认是否可以添加,就会往cube里添加一个seg信息,然后生成job。

EngineFactory.createBatchCubingJob(newSeg, submitter);

上边的工厂方法最终会调用类:BatchCubingJobBuilder2

BatchCubingJobBuilder2初始化会数据源和storage,一般这个都是根据model中的设置拿来的。

this.inputSide = MRUtil.getBatchCubingInputSide(seg);

上边会调用HiveSource.adaptToBuildEngine-->HiveMRInput,然后getBatchCubingInputSide 会调用一个内部类 BatchCubingInputSide 这个就是输入源的最终使用的类。

this.outputSide = MRUtil.getBatchCubingOutputSide2(seg);

上班会调用 HBaseStorage.adaptToBuildEngine---->HBaseMROutput2Transition,然后getBatchCubingOutputSide 会返回一个内部类 IMRBatchCubingOutputSide2 这个就是输出目标的要调用的类,这个类里边返回的是另一个类 HBaseMRSteps 这里会创建对应的任务参数等实际信息。

生成任务的时候,如果是来自输入端,任务一般就直接在输入端的类中生成任务,如果是有产出到输出端,比如落地最终的数据,需要压缩,或者需要载入等,都是storage端的工作,这个时候构建task的时候,就在输出端的类中。这个是kylin在输入和输出端都抽象出来一层,这是可插拔方式的一个实现。如果需要变更输入数据源或者变更输出数据源,就只需要写对应的类就可以了。

build() 这个方式是生成job任务的核心方法,构建实际的任务步骤。接下来咱们列出来每一个步骤都是做什么的。我后边会针对每一个任务有详细的源码追踪,列出来,大家一起学习。

1、inputSide.addStepPhase1_CreateFlatTable(result);

这个任务的调用就是生成打平数据表,调用的代码就是上边说的BatchCubingInputSide这个内部类里对应的addStepPhase1_CreateFlatTable 这个方法。这一步里边实际上可能会产生3个任务:

第一个任务就是生成打平数据的任务。
第二个任务是分区任务,如果设置了这个值的话。
第三个任务是如果有lookup表的话,会增加一个lookup的任务。

2、result.addTask(createFactDistinctColumnsStep(jobId));

这个任务主要是抽取出来事实表的列的唯一值,用来生成字典的。

3、result.addTask(createBuildDictionaryStep(jobId));

构建字典

4、result.addTask(createSaveStatisticsStep(jobId));

统计每个值大概有多少个。这样就可以根据数据类型和数据个数就能算出来总大小。这样就可以估算出目前整个表的大小。也就为后边的预分区提供了数据支持。

5、outputSide.addStepPhase2_BuildDictionary(result);

创建htable。根据上边的统计信息生成预分区。

6、addLayerCubingSteps(result, jobId, cuboidRootPath); // layer cubing, only selected algorithm will execute

构建cuboid数据。一个cuboid一个cuboid地构建。

7、addInMemCubingSteps(result, jobId, cuboidRootPath); // inmem cubing, only selected algorithm will execute

如果构建的时候,使用的inmem的构建算法,就会触发这个步骤。这个构建算法要求内存比较大。

8、outputSide.addStepPhase3_BuildCube(result);

生成hfile、然后把文件通过bulkload的方式加载到hbase中。

9、result.addTask(createUpdateCubeInfoAfterBuildStep(jobId));

完事儿了,就需要更新cube的信息。

10、inputSide.addStepPhase4_Cleanup(result);

清理临时数据,清理第一步生成的临时的宽表,清理任务运行过程中的临时目录。

11、outputSide.addStepPhase4_Cleanup(result);

这一步目前在2.3版本中是啥都没做的。在更新的版本中还没有关注,实际上这个应该删除过去的版本的那个hbase表。这个其实还是有历史数据的校验的问题,如果新生成的数据有问题,这个历史版本实际上是可以回滚出来的,这样线上的查询就不会有什么问题。但是实际上这块的逻辑做的有一些bug,如果不删除,怎么维护,因为如果当前seg数据量比较大的话,这个数据积累起来还是比较大的。而且没有地方维护来识别已经没用的hbase表的话,这些hbase表基本都很难识别。除非使用排除法来解决这个问题。

如果有一个meta的部分用来存储这个的话,问题就不大,既能主动cleanup这些数据,也能在线上build出问题的时候,进行数据回滚。

后续文章会依次进行每个任务的源码追踪。