Presto 从提交SQL到获取结果 源码详解(3)

物理执行计划

回到SqlQueryExecution.startExecution() ,执行计划划分以后,

 // 初始化连接,获取Connect 元数据,添加会话,初始ConnectId

metadata.beginQuery(getSession(), plan.getConnectors());

// 构建物理执行计划

// plan distribution of query

planDistribution(plan);

//检测Query Split 情况及自身状态

// 创建OutputBuffers

createInitialEmptyOutputBuffers

// 创建调度器,内部通过 QueryStateMachine 存放调度状态,递归创建Stages

createSqlQueryScheduler().createStages().createStreamingLinkedStages()

//提交调度任务至缓存

queryScheduler.set(scheduler);

// if query is not finished, start the scheduler, otherwise cancel it 开始执行
SqlQueryScheduler scheduler = queryScheduler.get();
if (!stateMachine.isDone()) {
    scheduler.start();
}

// 调度发起,生成并调度物理执行计划树

SqlQueryScheduler.schedule(Collection<SqlStageExecution> stages)

1.Fragment处理
2.构建物理计划(SqlQueryScheduler)
3.物理计划调度(SqlQueryScheduler)


生成Stage:


    SqlQueryScheduler对象递归创建Stage,每个Stage对应一个SqlStageExecution。
    创建Stage时,根据不同PartitionHandle中的分区策略(逻辑执行计划分段时确定不同分区策略),确定不同的split 调度策略 和 stage task调度策略

    2.1 分区策略:


        connector 自带的分区策略
        presto 系统分区策略,默认封装成 SOURCE_DISTRIBUTION

        读非桶表,partition策略为 SOURCE_DISTRIBUTION
        读桶表,partition策略根据桶数决定
        最后一个节点OutputNode,partition策略为 Single


    2.2 Stage调度:


            非桶表:SOURCE_DISTRIBUTION  均衡策略,split放置策略,逐个节点调度触发Task运行
            桶表:有splitSource但不是SOURCE_DISTRIBUTION:
                    有splitSource意外着TableScan,但不是SOURCE_DISTRIBUTION 说明 可能是直接读取桶表或Join时 读桶表一侧的情况
                    根据 RemoteSourceNode 的partition 类型来分别计算从split到node之间的对应关系
                    子Stage replicate : 上游复制到下游
                    子Stage repartition|gather : 上游Stage数据根据分区策略交付
            没有splitSource的Stage
                    又称RemoteSplit。这种Stage的split都是上游Stage生成的,因此不用调度Split,只需调度Task, 所有的split都需要task从上游Stage去拉取
            
            
            
            Split放置策略:
            非桶表:DynamicSplitPlacementPolicy:先把所有可以远程访问并且没有地址信息的split进行worker节点分配:
                    1.选择所有节点中,运行split数最小的节点,分发,实现均衡分配
                    2.轮询完,未分配节点:
                        2.1 远程不可访问,下发至对应本地。(可能失败)
                        2.2 集群split总数已满,随机分发,(可能失败)
            桶表:BucketedSplitPlacementPolicy


            RemoteSourceNode:子Stage源
            RemoteSourceNode.exchangeType:


            REPARTITION ,上游task为下游每一个partition准备一个 OutputBuffer ,而下游(Parent)task只会找上游(Children)task拉取自己的partition的数据。
            GATHER ,上游(Children)每个Task的OutputBuffer只有一个,下游(Parent)只有一个task
            REPLICATE ,上游(Children)的每个task的所有Output都放到一个Buffer中,下游(Parent)的每一个task会轮训上游(Children) Stage的所有task获取对应的这个task输出的全量数据。                    
                            
            split到node关系封装在BucketNodeMap中,

            不含分区时,  split -> bucket -> node
            构建split -> bucket的映射关系,根据桶号遍历
            构建bucket -> node的映射关系,随机选择节点

            含分区表时,split -> bucket -> partition -> node    分区到Node关系封装在NodePartitionMap

        Stage调度策略:
AllAtOnceExecution:所有stage按照Plan tree中自底向上逐个调度
PhasedExecution:coordinator根据Stage依赖关系,分批对这些Stage进行调度。 


Stage在被调度的过程中,如何决策往哪些Presto Worker上下发Task,发送多少个Task呢? 
首先对于源头Stage(包含TableScanNode),coordinator会尝试从connector获取对应TableScanNode的splits,split包含isRemotelyAccessible属性。


当remotelyAccessible=false时,该split只能被下发至addresses包含的Presto Worker上;
当remotelyAccessible=true时,该split可能被调度至集群内的任意一个Presto Worker上。
当该stage的split为第一次被调度至Presto Worker上时,coordinator就会往该Presto Worker下发Task。 
对于非源头Stage,coordinator会从Presto Worker中选择min(hashPartitionCount, aliveWokers)个worker,每个worker下发一个task。

调度:


    调度分为 Task调度 和  Split调度 ,不存在单独的split调度,因为split要运行在task中
    SqlQueryScheduler.schedule()
    多轮调度,依据ExecutionPolicy提供调度的Stage以及调度顺序,返回List<SqlStageExecution>。ExecutionPolicy维护Stage调度状态

    Presto默认使用AllAtOnceExecutionPolicy, 它会把所有的Stage一次性调度出去。
        Ps:一个后调度的Stage只是后调度,并不一定后执行,也并不需要等到前面先调度的Stage执行完才能开始执行或者被调度。
    每个Stage通过StageLinkage对象来维护和更新上下游的依赖关系和状态:
        通知下游Stage 有新数据可以pull, 通知上游要发起pull需求,上游需准备outputbuffer
        childOutputBufferManagers根据每一个子Stage的PartitioningHandle 维护一个OutputBufferManager,
             如果当前Stage调度出新Task,就会遍历childOutputBufferManagers,子Stage构建OutputBuffer 等待当前Stage来提取
             而OutPutBuffer 分为:BroadcastOutputBufferManager 和 PartitionedOutputBufferManager

调度类型:


    FixedCountScheduler(含splitSource):
        不读表的Stage,只调度Task 不调度split 无split依赖


    SourcePartitionedScheduler(不含splitSource,非桶表):
        非桶表调度,检测是否存在RunningTask,没有创建。
        不断从connector取splits,通过 DynamicSplitPlacementPolicy 确定目标节点。然后委托scheduleSplit 执行调度。


    FixedSourcePartitionedScheduler(不含splitSource,桶表):
        FixedSourcePartitionedScheduler先把task调度出去,再委托SourcePartitionedScheduler进行splitSource调度.

        为什么要这么做?
        因为FixedSourcePartitionedScheduler在构建的时候就已经知道调度的目标节点(存放在NodePartitionMap或者BucketNodeMap中)
        读桶表,确定了分区数量以及节点直接下发Task任务,待SourcePartitionedScheduler调度Split以后直接执行即可。
 


Split加载


    Coordinator中Splits加载和调度是并行处理的。
    对于一个分区表Scan,加载Splits步骤:1.获取partition 2.读取partiton目录,获取partiton的splits信息。
    
    所有表信息公共类叫:TableLayout 所有Connector都要实现ConnectorTableLayoutHandle,重写自己的方法

    主要接口:


        ConnectorSplitManager(引擎加载Split):
            getSplits():HiveConnector访问HMS获取partition,获取Split.此时委托HiveSplitLoader.BackgroundHiveSplitLoader.HiveSplitLoaderTask  多线程(hive.split-loader-concurrency配置并发度)异步执行,遍历所有Partition 边读取splits边进行splits调度


        HiveSplitLoaderTask.loadSplits() 内部加载split:
                fileIterators存储多个迭代器,单个Parititon对应多个迭代器对应多个splits, 迭代器加入队列中,执行加载split操作。
                当前迭代器为空时,partitions.poll()获取下一个partition的所有splits,重新遍历
            如果是bucket表,则调用getBucketedSplits()方法一次生成所有splits,如果不是bucket表,就调用createInternalHiveSplitIterator()来生成一个迭代器,然后逐步迭代
    
        ConnectorSplitSource(引擎汇报Splits给Coordinator):
            1.getNextBatch():获取下一个batch的splits  
            2.source.close()
            3.splits.isFinished

TaskExecutor 
    管理node上所有Task, Node内部使用cache thread pool多线程唤起TaskRunner 执行 SplitRunner


Split调度的优先级,由哪些因素决定?
    两个指标决定,关系 queues -> runners & levelScheduledTime
    level:决定了split被放到哪个queue里面执行。衡量标准是这个SplitRunner的运行已经消耗掉这个Task的时间积累量,computeLevel()内部 按照执行时间,分成了5个档次
    levelPriority:在相同的level里面,按优先级取出SplitRunner执行(每个level都有总调度时间,这个level已使用的调度时间占总调度时间的比例最小。个人理解:各个level按总时间平衡分配)
    

优先级是根据什么策略去更新?
每次一个SplitRunner调度完,这个SplitRunner都会进行优先级更新
    
SplitRunner优先级调度
    TaskRunner 调用 MultilevelSplitQueue.take() 获取 PrioritiesSplitRunner
    
更新以后怎么根据优先级来确定SplitRunner的调度顺序的?
TaskRunner的执行策略是不断从waitingSplit中取出SplitRunner来执行,执行1s
    若已结束,则执行isFinished
        1.运行时间统计
        2.TaskHandle清理SplitRunner相关信息
        3.TaskHandle取新的SplitRunner,调度执行

        
当TaskRunner的线程选出对应的 SplitRunner的process
每一个Driver实际上属于一个Pipeline, 然后Pipeline属于task, task 属于 stage

摘抄出处:Presto(Trino)分布式(物理)执行计划的生成和调度_presto fixedsourcepartitionedscheduler-CSDN博客

  • 20
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值