分布式SQL查询引擎原理(以Presto SQL为例)

2022-02-14 00:00:00 数据 执行 生成 调度 计划

本文主要以Presto SQL为例来介绍典型的分布式SQL查询引擎的执行模型(Query Execution Model)及原理,此文篇幅较长,3w字长文,20幅原理图,信息量与干货居多,是到目前为止行业内一篇全面介绍Presto SQL执行原理的硬文,综合考虑拆开到多篇文章不合适所有都凑在一起了,请读者耐心阅读,一定有收获。

介绍Presto SQL的执行原理前,需要读者具备一定的SQL与大数据的基础知识,请先阅读:

  • 先导篇一:Presto概述:特性、原理、架构 https://zhuanlan.zhihu.com/p/260399749
  • 先导篇二:Presto的应用场景与企业案例 https://zhuanlan.zhihu.com/p/260653669
  • 先导篇三:常见开源OLAP技术架构对比 https://zhuanlan.zhihu.com/p/266402829
  • 先导篇四:学会使用PrestoSQL https://zhuanlan.zhihu.com/p/260660023

如果你感兴趣请关注知乎专栏《深入浅出Presto:PB级OLAP引擎》,获取更多干货:https://www.zhihu.com/column/c_1294277883771940864

1. 从架构上看SQL Query的执行流程

请参见上面的架构图,从用户开始写SQL开始到查询结果返回,我们划分出以下几个部分:

  • SQL Client:用户可以在这里输入SQL,它负责提交SQL Query给Presto集群。SQL Client一般用Presto自带的Presto Client比较多,它可以处理分批返回的结果,并在终端展示给用户。
  • External Storage System:由于Presto自身不存储数据,计算涉及到的数据及元数据都来自于外部存储系统,如HDFS,AWS S3等分布式系统。在企业实践经验中,经常使用HiveMetaStore来存存储元数据,使用HDFS来存储数据,通过Presto执行计算的方式来加快Hive表查询速度。
  • Presto Coordinator:负责接收SQL Query,生成执行计划,拆分Stage和Task,调度分布式执行的任务到Presto Worker上。
  • Presto Worker:负责执行收到的HttpRemoteTask,根据执行计划确定好都有哪些Operator以及它们的执行顺序,之后通过TaskExecutor和Driver完成所有Operator的计算。如果个要执行的Operator是SourceOperator,当前Task会先从External Storage System中拉取数据再进行后续的计算。如果后一个执行的Operator是TaskOutputOperator,当前Task会将计算结果输出到OutputBuffer,等待依赖当前Stage的Stage来拉取结算结果。整个Query的所有Stage中的所有Task执行完后,将终结果返回给SQL Client。

2. 从代码中看SQL执行流程

先介绍两个概念:

  • SQL是声明式的(declarative):声明式指的是你描述的就是你想要的结果而不是计算的过程,如数据工程师用SQL完成数据计算时既是如此。与之相反的是过程式的,如你写一段Java代码,通篇都是在描述如何完成计算的过程,而只有到了结尾才return你想要的结果。声明式可以说是结果导向的,它不关心实现过程,所以普遍来说,写SQL比写Java代码更简单易懂,更容易上手,甚至可以面向没有编程经验的数据分析师。
  • 执行计划(Execution Plan):但是SQL背后的实现过程,总得有代码去实现吧,而且在很多情况下还要兼顾功能、性能、成本等因素,可以说是非常复杂的,这就是SQL的执行引擎需要去考虑和实现的。既然SQL是声明式的,不关心实现过程,那么SQL执行引擎如何才能知道具体的执行步骤和细节呢?这里就需要引入一个执行计划的概念,它描述的是SQL执行的详细步骤和细节,SQL执行引擎只要按照执行计划执行即可完成整个计算过程。当然在这之前,SQL执行引擎需要做的件事是先解析SQL,生成SQL对应的执行计划。

以上两个概念,套用到任意类SQL的执行系统都是适用的,例如MySQL,Hive,SparkSQL,Clickhouse。具体而言,对于Presto来说,它的SQL执行过程可以详细拆解为以下几个步骤:

  • 步:【Coordinator】接收SQL Query请求
  • 第二步:【Coordinator】词法与语法分析(生成AST)
  • 第三步:【Coordinator】创建和启动QueryExecution
  • 第四步:【Coordinator】语义分析(Analysis)、生成执行计划LogicalPlan
  • 第五步:【Coordinator】优化执行计划,生成Optimized Logical Plan
  • 第六步:【Coordinator】为逻辑执行计划分段(PlanFragment)
  • 第七步:【Coordinator】创建SqlStageExecution(创建Stage)
  • 第八步:【Coordinator】Stage调度-生成HttpRemoteTask并分发到Presto Worker
  • 第九步:【Worker】在Presto Worker上执行任务,生成Query结果
  • 第十步:【Coordinator】分批返回Query计算结果给SQL客户端

从步到第八步,主要描述的是Presto对一个传入的SQL语句如何进行解析并生成终的执行计划,生成Query执行计划的主要流程如下图所示:

可以看到,自步至第九步,全部是在Presto Coodinator上完成,足见Coordinator的核心地位,阅读Presto源码时你也会发现它的代码是极其复杂的,我前前后后阅读了十几遍,debug了几十遍才有了今天的这份自信来将我的经验输出到互联网上帮助你更高效的掌握它;第十步是在Presto Worker上执行;第十一步是在Presto Coordinator上执行,并将查询结果分批返回给客户端(如Presto SQL Client或者其他JDBC客户端)。

接下来几个章节我们也会逐一详细介绍以上的每个步骤,这里先对每一步做一些概要介绍。

3. SQL执行分步拆解

为了能够让内容更具体生动,我们选了一个典型的SQL,TPC-DS的Query55,以它为例来介绍SQL执行过程。

假设现在有一个Presto用户,在Presto-Cli里面输入SQL,然后提交执行:

use tpcds.sf1;

-- For a given year, month and store manager calculate the total store sales of any combination all brands.
select i_brand_id as brand_id, i_brand as brand,
        sum(ss_ext_sales_price) as ext_price
from date_dim, store_sales, item
where d_date_sk = ss_sold_date_sk
        and ss_item_sk = i_item_sk
        and i_manager_id=82
        and d_moy=8
        and d_year=1999
group by i_brand, i_brand_id
order by ext_price desc, i_brand_id
limit 10;

相关文章