presto 核心设计
1 RESTFUL架构
在presto中几乎所有的操作都是依赖于AirLift构造的RESTful服务来完成的,包括worker节点的管理、查询语句的提交、查询状态的显示、各个task之间数据的传递等。因此presto中的RESTful服务是presto集群的基础。
presto中提供了四种类型的RESTful接口,分别是statement服务接口、query服务接口、stage服务接口、task服务接口
1.statement服务接口
与sql语句相关的请求均由该服务接口处理,包括接收提交的sql语句、获取查询执行结果的语句、取消查询语句等。statement服务接口的实现类为StatementResource。
2.query服务接口
与查询相关 的RESTful请求均由query服务接口处理,包裹sql语句的提交、获取查询执行的结果、取消查询等。query服务接口实现类为QueryResource。
3.stage服务接口
与stage相关的RESTful请求均由stage服务接口处理,其实该接口只提供了一个功能,就是取消或者结束一个指定的stage。stage服务接口的实现类为StageResource。
4.task服务接口
与task相关的RESTful请求均由ask服务接口处理,包括task的创建、更新、状态查询和结果查询等。task服务接口的实现类为TaskResource
presto集群中的数据传输、节点通信、心跳感应、计算监控、计算调度和计算分布全部都是基于RESTful服务实现的,因此presto中的RESTful服务就是presto所有服务的基石。
2 提交查询
终端用户可以用过jdbc或者cli提交查询语句,也可以通过第三方机构或者个人使用python、c语言开发的驱动提交查询。presto客户端对查询语句的提交主要分为三个步骤。
从指定的文件、命令行参数或者cli窗口中获取需要执行的sql语句。
将得到的sql语句组装成一个RESTful请求,发送给Coordinator,并处理返回的response。
cli会不停的循环分批读取查询结果并在屏幕进行动态展示,直到查询结果完全显示完毕。
查询流程图
3 生成查询计划
本章主要讲述presto对一个传入的sql语句如何进行解析并生成最终的执行计划。
从上图可以看到,生成查询计划分成语法分析、词法分析、语义分析、执行计划生成、执行计划优化、执行计划分阶段执行。
1基本概念
11node
查询语句经过词法和语法分析之后,会生成抽象语法树(AST),该语法树中的每一个节点都是一个Node(SQL语句的一部分,如select、where、group by等)。Node是一个抽象类,实现类如下:
- approximate 用于近似查询
- explainOption 标识explain语句的可选参数,有explainFormat和explainType两类。explainFormat标识输出结果的格式,有text和graphviz两种类型。explainType标识explain语句的类型,有logical和distributed两类,分别标识生成逻辑执行计划与生成分布式执行计划。
- expression 标识sql语句中出现的表达式。
- frame bound 用于窗口函数中滑动窗口的可选参数。
- relation 是一个抽象类,标识多个节点之间的关系,如join、union等。
- select 标识查询语句中的select部分。
- select item 标识select语句中的列类型,有allcolumns和singlecolumns两种类型。
- sort item 标识排序的某一列及其类型。
- statement 标识presto能使用的sql类型的sql语句。
- table element 标识建表语句描述表的每一列,包括列名与类型。
- window 表示一个窗口函数。
- window Frame 表示窗口函数中欢动窗口的可选参数。
- with 表示一个查询中所有的with语句,主要元素有recursive、querys。
- with query 表示一个with语句,主要元素有name、query、columnNames。
12metadata API
metadata API即是matadata接口,其提供了对源数据进行各种操作的接口,列如列出所有的数据库名、表名等。这些接口在对sql进行语义分宜以及某些ddl操作(如create table)的执行过程中会用到。
metadata api将不同Connector对其元数据的各种啊哦做抽象成一了统一的接口,使得在使用这些接口时无需考虑具体的底层connector实现。
metadata api除了提供对元数据操作的接口,还提供了一些通用的与connector无关的方法,例如列出presto支持的自定义函数等。
2 词法和语法分析
presto的此法于语法分析是封装在SQLQuerymanager的createQuery方法中。
21语法规则
presto使用ANTLR4编写sql语法,语法规则的定义在presto-parse项目的sqlbase.g4文件中,通过ANTLR4查看该文件的语法图。
22词法分析
SQLParse的createStatement方法调用其内部方法invokeParser。
23语法分析
presto使用visitor模式对sql语句进行语法分析。
3获取查询执行引擎
queryexecution表示一次查询执行,用于启动、停止与管理一个查询,以及统计这个查询的相关信息。
3.1 获取queryExecutionFactory
根据statement类型获取相对应的QueryExecutionFactory。QueryExecutionFactory是一个接口,其实现类有DataDefinitionExecutionFactory以及SqlQueryExecutionFactory。 executionFactories则是一个Map,存储了不同的Statement类型与QueryExecutionFactory实现类的对应关系,该map的初始化实在CoordinatorModule中进行的,对应关系如表:
…
总结:create table 、rename table 等ddl操作的sql语句对应了DataDefinitionExecutionFactory,而非ddl操作的sql语句。例如select、insert等对应了SqlQueryExecutionFactory。
3.2 创建QueryExecution
当以上的词法与语法分析出错,照着找不到statement实现类与QueryExecutionFactory实现类的对应关系时,将创建一个FailedQueryExecution,并封装错误信息,最后返回给用户。
调用之前获取的QueryExecutionFactory的createQueryExecution方法,获取对应的QueryExecution。DataDefinitionExecutionFactory创建的是DataDefinitionExecution,而 SqlQueryExecutionFactory创建的是SqlQueryExecution。
在DataDefinitionExecutionFactory创建DataDefinitionExecution时,根据statement类型将对应的 DataDefinitionExecutionTask实现类与DataDefinitionExecution绑定。
3.3启动QueryExecution
获取QueryExecution之后,SqlQueryQueueManager方法将QueryExecution与配置的查询队列规则进行匹配,如匹配成功且队列未满,则将QueryExecution加入匹配队列。
查询队列按照 FIFO规则调度查询。最后启动QueryExecution。
DataDefinitionExecution启动直接调用其绑定的DataDefinitionTask实现类的execute方法即可。以dropTable为例,由于DropTable与dropTableTask绑定,就会执行DropTableTask 的execute方法。 SqlQueryExecution启动比较复杂,需要执行查询计划、优化查询计划、分阶段执行查询计划。
语义分析
由于DataDefinitionExecution的执行直接调用DataDefinitionTask实现类的execute方法,并未经过执行计划生成的步骤,故以下的内容只针对SqlQueryExecutionFactory。
statement分析
statementAnalyzer是对statement进行予以分析的类,针对不同的statement实现类进行语义分析。
relation分析
TupleAnalyzer类是对Query中的Relation进行分析的类。
表达式分析
ExpressionAnalyzer类对sql语句中的表达式进行分析,主要功能如下:
- 获取表达式的类型
- 获取需要进行类型转换的表达式及其转换的目的类型。
- 获取表达式中存在的函数信息。
- 获取表达式中所有合法的列名及对应列的编号。
- 获取表达式中In语句中的子查询。
执行计划生成
LogicalPlanner类会根据以上针对SQL语句分析所得的结果,生成逻辑执行计划。
执行计划节点
执行计划树中的节点类型
节点 | 名称 |
---|---|
AggregationNode | 聚合操作的节点,聚合的类型有Final、Partial、Single三种,分别表示最终聚合、局部聚合和单点聚合,其中执行计划在进行优化之前,聚合的类型都是单点聚合,在执行计划优化器中会对其进行拆分成局部聚合和最终聚合。 |
DeleteNode | 用于Delete操作的节点 |
DistinctLimitNode | |
ExchangeNode | 用于在执行计划中不同stage之间交换数据的节点,出现在逻辑执行计划中 |
FilterNode | 进行过滤操作的节点 |
IndexJoinNode | 用于对Index Join操作的节点 |
IndexSorceNode | 与Index join配合使用的执行数据源读取操作的节点 |
JoinNode | 执行Join操作的节点 |
LimitNode | 执行limit操作的节点 |
MarkDistinctNode | 用于处理一下outputNode、projectNode的sql语句的节点 |
OutputNode | 输出最终结果的节点 |
project | 用于进行列映射的节点,用于将ProjectNode下层节点输出的列映射到Project上层节点输入的列 |
RemoteSourceNode | 类似于ExchangeNode,用于分布式执行计划中不同的stage之间交换数据,出现在分布式执行计划中 |
RowNumberNode | 用于处理窗口函数row_number |
SampleNode | 用于处理抽样函数 |
SemiJoinnode | 用于处理执行计划生成过程中产生的SemiJoin |
SortNode | 用于排序操作 |
TableCommitNode | 用于对create table as select语句、insert语句、delete语句的操作执行commit |
TableScanNode | 用于读取表的数据 |
TableWriterNode | 用于向目的的表写入数据 |
TopNNode | 用于取数据排序后的前N条结果,使用效率更高的TopN算法,而不是对所有数据进行全局派去在取前N条 |
TopNRowNumberNode | 用于处理窗口函数row_number中排序前N条记录,使用效率更高的TopN算法。 |
UnionNode | 用于处理Union操作 |
UnnestNode | 用于处理Unnest操作 |
ValuesNode | 用于处理Values语句 |
WindowNode | 用于处理窗口函数 |
sql执行计划
RelationPlanner用于针对Relation类型的sql语句生成执行计划。
1、table
visitTable对table进行分析主要分为以下两步。如果该table是with所定义的表明,或者该table实际是一个view,则处理其所关联的查询生成执行计划。如果该table是普通的表,则构建TableScanNode。
2、AliasedRelation
visitAliasedRelation处理AliasedRelation所关联的relation,并生成执行计划。
3、SampledRelation
visitSampledRelation处理SampledRelation分为处理其关联的relation,生成执行计划树。构建一个sampleNode,添加到以上的执行计划树之上。
4、join
visitjoin处理join分为处理join左侧的relation,生成左侧执行计划树。如果join右侧是unnest且join类型为cross join或者Implicit join,则根据unnest构造一个UnnestNode以及一个ProjectNode,添加到左侧 执行计划树智商并返回。
query执行计划
queryplanner用于处理query和querySpecification。
执行计划优化
生成执行计划之后,会对所生成的执行计划进行优化,目前presto只支持基于规则的优化器。现有的优化器包括如下几种。
ImplementSampleAsFilter
将bernoulli抽样的samplenode改写为filternode,filternode的过滤条件为 rand() < SampleRatio
CannonicalizeExpressions
将执行计划中设计的表达式进行标准化,标准化的主要工作有。
1 | is not null 改写为 not(is null) |
SimplifyExpressions
对执行计划中设计的表达式进行简化和优化处理,具体可查看ExpressionInterpreter。
UnaliaseSymbolReferences
用于去除执行计划中projectnode中的无异议映射。
执行计划分段
经过执行计划生成与执行计划优化之后,最后对执行计划进行分段。
执行计划分段 | 描述 |
---|---|
source | source阶段是从数据源的表中读取数据的阶段,一般包括tableScanNode和projectNode,以及可能存在的filterNode等。 |
fixed | fixed阶段位于source阶段之后,该阶段将source阶段读取的数据分散到多个节点上进行处理,主要处理的操作有局部聚合、局部join、局部数据写入表等。 |
single | single阶段位于fixed阶段之后,只在单个节点上执行,用于汇总所有的处理结果,例如针对局部聚合的数据进行最终聚合,并将结果传输给coordinator。 |
Coordinator_only | Coordinator_only阶段只在coordinator上执行,对insert和create table操作进行commit的tableCommitNode属于Coordinator_only阶段。 |
举例
1 | Fragment 0 [SINGLE] |
1 | Fragment 0 [SINGLE] |