Presto查询调度解析

本文深入解析了Presto的查询调度过程,从提交查询到启动调度器,再到不同类型的Stage调度策略,包括Source Stage、Fixed/Single Stage以及可扩展分布式写出Stage的调度。文章详细阐述了Stage调度器的创建以及节点分配策略,展示了Presto如何动态地分配和执行查询任务。
摘要由CSDN通过智能技术生成


Presto将查询拆分成多个且有树状层级关系的Stage,实际就是将整个SQL处理过程拆分成多个具有各自功能的执行阶段。Presto将查询请求解析成各个执行阶段后,便会将各个阶段分配到各个计算节点中执行,这个分配的过程实际是基于Stage进行的,不同的Stage有不同的调度策略。每个执行阶段都会被进一步分解为若干个Task,查询调度就是分配Task到Worker节点并发协同完成整个查询的过程。

提交查询

首先来简单看下从用户提交查询到真正执行的整个过程。presto通过Rest接口QueuedStatementResource.postStatement提交查询,其只是简单地将查询加入到一个映射队列中Map<QueryId, Query> queries = new ConcurrentHashMap<>()。触发查询实际执行是调用查询任务状态的接口,其代码调用栈如下,可以看到最后实际是通过SqlQueryExecution.start方法进行查询的执行。

QueuedStatementResource.getStatus
	Query.toResponse
		Query.waitForDispatched
			DispatchManager.createQuery
				DispatchManager.createQueryInternal
					LocalDispatchQueryFactory.createDispatchQuery
						SqlQueryManager.createQuery
							SqlQueryExecution.start

生成查询计划并启动调度器

通过SqlQueryExecution.start方法执行实际的查询,其主要的执行流程如下:

  • 将当前线程名更改为Query-QueryId,从而可以跟踪具体某个查询的执行;

  • 将状态机转换到planning状态,通过状态机stateMachine控制各个阶段的流转;

  • SqlQueryExecution.analyzeQuery方法生成逻辑执行计划;

  • SqlQueryExecution.planDistribution方法获得Stage的分布式执行计划,并生成查询调度器(默认为SqlQueryScheduler实现);

  • 将状态机转换到starting状态;

  • 启动查询调度器queryScheduler进行实际查询任务的调度执行;

      // SqlQueryExecution.java
      public void start()
      {
      	// 设置当前线程名
          try (SetThreadName ignored = new SetThreadName("Query-%s", stateMachine.getQueryId())) {
              try {
                  // 将状态机转换到planning状态
                  if (!stateMachine.transitionToPlanning()) {
                      // 如果查询已经启动或结束了,则直接返回
                      return;
                  }
    
                  // 分析查询,生成逻辑执行计划
                  PlanRoot plan = analyzeQuery();
    
                  // 生成查询的分布式执行计划(创建并设置queryScheduler,默认为SqlQueryScheduler实现)
                  planDistribution(plan);
    
                  // 将状态机转换到starting状态
                  if (!stateMachine.transitionToStarting()) {
                      // 如果查询已经启动或结束了,则直接返回
                      return;
                  }
    
                  // 如果查询还未完成,则启动scheduler调度器,否则取消它
                  SqlQuerySchedulerInterface scheduler = queryScheduler.get();
                  if (!stateMachine.isDone()) {
                      scheduler.start();
                  }
              }
          }
      }
    

对于每一查询,Presto都会生成一个与其对应的SqlQueryScheduler实例,可称之为整个查询的调度执行器。

查询调度过程

查询调度过程的调用栈如下:

SqlQueryScheduler.start
	SqlQueryScheduler.startScheduling
		SqlQueryScheduler.schedule

其主要逻辑实现在SqlQueryScheduler.schedule方法中,可以看到其实际是按stage来进行调度的。其主要执行流程如下:

  • 获取所有处于执行就绪状态的stage加入到待调度队列中;

  • 遍历待调度队列中的stage并应用运行时CBO优化其执行计划;

  • 为每个stage添加相应的执行策略;

  • 遍历待调度队列中的stage并进行调度执行;

  • 不断循环直至查询执行完成,并在某个stage执行完成时更新其状态以及下游stage的输入数据位置信息,从而可以让下游stage也转变成就绪状态可以被调度执行;

      // SqlQueryScheduler.java
      private void schedule()
      {
      	// 如果仍然在调度前一批次的stage,则直接返回
          if (!scheduling.compareAndSet(false, true)) {
              return;
          }
      	// StageExecutionAndScheduler包含stage执行器和调度器实例,以及stage间的血缘关系
          List<StageExecutionAndScheduler> scheduledStageExecutions = new ArrayList<>();
      	// 设置当前线程名
          try (SetThreadName ignored = new SetThreadName("Query-%s", queryStateMachine.getQueryId())) {
              Set<StageId> completedStages = new HashSet<>();
    
              List<ExecutionSchedule> executionSchedules = new LinkedList<>();
      		// 如果当前线程未被中断,则不断循环
              while (!Thread.currentThread().isInterrupted()) {
                  // 遍历executionSchedules,并移除所有已完成部分
                  executionSchedules.removeIf(ExecutionSchedule::isFinished);
    
                  // 获取已就绪可以运行的更多的部分(深度先序遍历执行计划树,并限制最大并发数)
                  List<StreamingPlanSection> sectionsReadyForExecution = getSectionsReadyForExecution();
    
                  // 如果所有部分都已运行完成,则退出循环
                  if (sectionsReadyForExecution.isEmpty() && executionSchedules.isEmpty()) {
                      break;
                  }
    
                  // 在创建SectionExecutions前对就绪部分应用运行时CBO优化
                  List<SectionExecution> sectionExecutions = createStageExecutions(sectionsReadyForExecution.stream()
                          .map(this::tryCostBasedOptimize)
                          .collect(toImmutableList()));
      			// 如果查询状态机的状态是已完成,则将就绪的sectionExecutions全部终止,并退出循环
                  if (queryStateMachine.isDone()) {
                      sectionExecutions.forEach(SectionExecution::abort);
                      break;
                  }
      			// 将所有部分的stage添加到scheduledStageExecutions中,然后为每个部分创建相应的执行策略executionPolicy并添加到executionSchedules中
                  sectionExecutions.forEach(sectionExecution -> scheduledStageExecutions.addAll(sectionExecution.getSectionStages()));
                  sectionExecutions.stream()
                          .map(SectionExecution::getSectionStages)
                          .map(executionPolicy::createExecutionSchedule)
                          .forEach(executionSchedules::add);
    
      			// 如果待执行调度的部分非空并且不是已执行完成则执行循环
                  while (!executionSchedules.isEmpty() && executionSchedules.stream().noneMatch(ExecutionSchedule::isFinished)) {
                      List<ListenableFu
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值