presto执行过程,sql支持及hive异同

Prestodb概述及性能测试

概述内容

(1)简介

(2)Hive and Prestodb, comparison of functionality

(3)Hive and Prestodb, comparison of performance

 

(1)简介

Presto是由facebook开发的一个分布式SQL查询引擎, 它被设计为用来专门进行高速、实时的数据分析。它支持标准的ANSI SQL,包括复杂查询、聚合(aggregation)、连接(join)和窗口函数(window functions)。

Presto框架图如下:



     下面的架构图中展现了简化的Presto系统架构。客户端(client)将SQL查询发送到Presto的协调员(coordinator)。协调员会进行语法检查、分析和规划查询计划。计划员(scheduler)将执行的管道组合在一起,将任务分配给那些里数据最近的节点,然后监控执行过程。客户端从输出段中将数据取出,这些数据是从更底层的处理段中依次取出的。

     Presto的运行模型和Hive或MapReduce有着本质的区别。Hive将查询翻译成多阶段的MapReduce任务,一个接着一个地运行。每一个任务从磁盘上读取输入数据并且将中间结果输出到磁盘上。然而Presto引擎没有使用MapReduce。它使用了一个定制的查询和执行引擎和响应的操作符来支持SQL的语法。除了改进的调度算法之外,所有的数据处理都是在内存中进行的。不同的处理端通过网络组成处理的流水线。这样会避免不必要的磁盘读写和额外的延迟。这种流水线式的执行模型会在同一时间运行多个数据处理段, 一旦数据可用的时候就会将数据从一个处理段传入到下一个处理段。这样的方式会大大的减少各种查询的端到端响应时间。



 

 

(2)Hive and Prestodb, comparison of functionality

        √: Yes; ×: No; Blue: The main differences between hive and presto

 

 

hive 0.11.0

presto 0.56

Implement

Java

Java

DataType

 

integer

string

floating point

boolean

map

list

struct

uniontype

×

timestamp

DDL(数据定义语言)

 

create/alter/drop table

×

create view

×

truncate table

×

desc

create index

×

DML(数据操作语言)

 

load data

×

insert

explain

tablesample(基于column做bucket)

group by

order by

having

limit

inner/left/right/full join

union

sub queries

Enhanced Aggregation, Cube, Grouping and Rollup

×

lateral view

×

Function

 

UDF

×

Mathematical Functions

String Functions

Date and Time Functions

Regex

Type Conversion Functions

×

Conditional Functions

Aggregate Functions

Windowing

Distinct

Url

Json

 

功能上,Presto与Hive有几个不同的地方,也可以说是Presto功能不完善,毕竟Presto推出时间不长,详见如下:

1. Presto完成没有数据写入功能,不能使用create语句建表(可通过CREATE TABLE tablename AS query),建立视图、导数据。

2. Presto不支持UDF(用户自定义函数)。

       3. Presto支持窗口函数,但比Hive相对较少。

 

(3)Hive and Prestodb, comparison of performance

测试环境如下:



     由于部分机器涉及应用,暂用4台机器作为prestodb的集群,prestodb所有运算都在内存,所以配置大内存有助于提高prestodb的运算速度(现配置4G)。

以下为具体的测试结果:

记录数:169984827

DML

Hive(s)

Prestodb(s)

limit

5.493

0.05

where

49.255

0.05

count(*)

184.974

86

group by

161.633

110

sub queries

105.686

0.09

join

657.006

177

注:prestodb查询时间只精确到秒,后带小数忽略



 

参考资料

Prestodb官网:http://prestodb.io/

ZOL频道:http://jishu.zol.com.cn/78874.html

 

1 Presto概览
1.1 presto设计思想及特点
多数据源:且支持扩展

计算方式:完全基于内存进行计算,并没有使用mapReduce。

支持标准SQL:

pipeLine设计:

这个pipeLine如何理解???

 

1.2 基础架构及执行过程
典型的主从架构,coordinator负责调度,worker上的进程负责接受调度,执行具体的task。每个task读入具体的split,并进行处理。

 

横向的一条代表:不同阶段的任务, 下面的work代表执行的实体。左边的work负责对SqlQueryExecution进行解析,输出的结果是SqlStageExecution。这个SqlStageExecution其实代表的是多个subPlan。其中有源数据读取的,也有进行汇总计算的。

然后根据各个节点的情况进行任务分发,到被分发的到节点上就是SqlTaskExecution。然后执行。

note:presto形成的逻辑执行计划进一步进行了拆分,这个拆分是为了基于内存的并发计算。

1.3 基本概念
模型:

connector

note: presto-main/etc/catalog/**properties

coordinator

worker

catalog:代表数据源比如tcph和hive之类

schema:代表一张二维表

 

查询:

概念

实体

功能

 

Stagement

StagementResource

SQL语句

getQueryReuslt/createQuery

Query

QueryResource

查询执行

getQueryReuslt/createQuery

除了语句还附加了配置信息,执行和优化信息

Stage

coordinator

ddl/dml

 

single

顶层聚合

返回结果给client

fixed

中间聚合

中间计算

source

读取源数据

数据的scan/filter/project

Exchange

 

完成stage之间的数据交换

Exchange Client 和output Buffer

Task

 

包含一个或者多个Driver

stage拆分成多个task可以并发执行

每个task有对应的输入输出,

每个task处理一个或者多个split

Driver

 

Driver表示对某个split的Operator的集合,

1 一个Driver处理一个split,拥有一个输入和输出

2 是一个split上一系列操作的集合

3 没有子类

Operator

Limit/Orderby/HashJoin

TableScan

代表对split的一种操作

1 比如过滤,加权,转换

2 输入和输出都是Page对象

Split

 

数据分片

 

Page

 

代表小型二维表

根据字段数的大小确定block对象去多少行。

一个Page不超过1M

不然MySQL上的split怎么理解??

block

 

接口

数组(long/int/byte)

一个block对象存储一个字段的若干行

1 stage是个在coordinator生成的抽象的概念,task及其以下是运行在具体的work上的。

2 task之间是流式的,task内部并不是流式的

1.4 demo
https://cloud.tencent.com/developer/article/1032986

2 源码分析
2.1 工程结构


模块说明:

presto 客户端代码在presto-cli模块 但presto服务端代码不在presto-server,打开presto-server模块可以发现他采用maven的插件进行编译打包,改模块只有该规则文件。

 presto服务端代码在presto-main模块,PrestoServer类启动服务器。

 presto-base-jdbc 关系型数据库连接器的公共模块 

presto-mysql mysql连接器用到了presto-base-jdbc presto-jdbc jdbc客户端/另一种是cli 

presto-hive-* hive连接器相关代码 presto-orc hive连接器读取hdfs的orc文件,

并做了一些优化 presto-ml machine learning,未实现,打算ing presto-kafka/cassadra/jmx 各种连接器

一般使用presto可能需要进行功能拓展以进行二次开发,比如会有一些自定义高级函数

presto_main结构:

普通函数和窗口函数集中在此。

 

2.2 执行过程
2.2.1 模块概览
module

class

method

note

client

presto

console.run

构建query,向server发起query

Console

executeCommand()

process(queryRunner, split.statement(), outputFormat, () -> {}, false)

queryRunner.startQuery(finalSql)

QueryRunner

new Query(startInternalQuery(session.get(), query), debug)

newStatementClient(client, session, query)

StatementClientFactory

new StatementClientV1(httpClient, session, query)

StatementClientV1

url = url.newBuilder().encodedPath("/v1/statement").build()

coordinate

StatementResource

coordinator上的重要类,为client和worker提供restful服务

createQuery:

将query存入一个对象

SimpleLocalMemoryContext?

StatementResource

getQueryResults

cancalQuery

该构造函数从client来的时候貌似没有传参?构造函数中执行调度

PurgeQueriesRunnable

run

为啥没有执行query,下一步往哪执行

QueryResource

createQuery

为啥到了QueryResource,从哪里执行到该方法的。

SqlQueryManager

createQuery

创建QueryExecution

QueryExecution

(sqlQueryExecution)

start

1 分析查询得到执行计划

2 创建调度器

3 scheduler.start();

SqlQueryScheduler

start

QueryExecution.submit

分发plan成task到worker上

 

TaskResource

 

 

 

...

 

 

ResultQuery

StatementResource

asyncQueryResults()

发起查询之后就一直获取查询状态

 

2.2.2 代码执行链
接上图:

Rer*********************************  statementResource生成    *********************************************************
StatementResource.createQuery    //创建并执行查询
    Query.create(statement,...)
        Query result = new Query()  //返回结果Result
            QueryInfo queryInfo = queryManager.createQuery(sessionContext, query)
                Statement wrappedStatement = sqlParser.createStatement(query, createParsingOptions(session));
                queryExecution = queryExecutionFactory.createQueryExecution(queryId, query, session, statement, parameters);
                resourceGroupManager.submit(statement, queryExecution, selectionContext, queryExecutor);
                    groups.get(selectionContext.getResourceGroupId()).run(queryExecution);
                        executor.execute(query::start);
                            PlanRoot plan = analyzeQuery();
                            metadata.beginQuery(getSession(), plan.getConnectors());
                            planDistribution(plan); //由plan构建task并进行分发调度计划,这个调度计划体现在Scheduler上
                                                                StageExecutionPlan outputStageExecutionPlan = distributedPlanner.plan(plan.getRoot(), stateMachine.getSession()); // 获得stage的执行计划
                                SqlQueryScheduler scheduler = new SqlQueryScheduler();//完成了sqlStageExecution和stageSchedulers的创建
                                    List<SqlStageExecution> stages = createStages();  //根据不同的情况,生成对应的stage;体现为不同的StageScheduler和SqlStageExecution
                                                                                SqlStageExecution stage = new SqlStageExecution() //
                                        stageSchedulers.put(stageId, SourcePartitionedScheduler||new FixedCountScheduler(stage, partitionToNode||FixedCountScheduler)); //根据handle类型
                            SqlQueryScheduler.start();//分发计划体现在plan创建的Scheduler上
                                SqlQueryScheduler##executor.submit(this::schedule); //Async, 循环执行,直到 execution 完成.
                                    SqlQueryScheduler##stageSchedulers.get(stageId).schedule();  //使用上述 StageScheduler 的实现类来分发 Task 到工作节点
                                                                            taskScheduler.apply(entry.getValue(), entry.getKey())); //对该函数(this.taskScheduler = stage::scheduleTask;)使用这两个参数执行
                                                                                sqlStageExecution::scheduleTask; // 在new StateSchedule中构造函数中获取remoteTasks赋值给其属性taskScheduler
                                          RemoteTask(HttpRemoteTask) task = remoteTaskFactory.createRemoteTask() //
                                          task.start(); //HttpRemoteTask-%s
                                            scheduleUpdate(); 
                                                HttpRemoteTask.sendUpdate(); // 异步请求
                                                     HttpClient.executeAsync(): // 发送请求到 TaskResource.createOrUpdateTask()                            
    asyncQueryResults    
StatementResource.getQueryResutl()    //  分批获取查询结果,client向coor不断请求,每次获得部分结果,就是由该方法处理的。
    asyncQueryResults
note:查询请求上的token是用来保证分批查询结果的顺序的
 
*********************************  SqlTask    *********************************************************
Reponse TaskResource.createOrUpdateTask(TaskId,TaskUpdateRequest,UriInfo) // 这个request请求的中就含有Fragment信息,后续会根据Fragment信息创建SqlTaskExecution
    TaskInfo taskInfo = taskManager.updateTask(session,...)
        sqlTask.updateTask(session, fragment, sources, outputBuffers);
            SqlTaskExecution SqlTaskExecutionFactory.create(Session session, QueryContext queryContext, TaskStateMachine taskStateMachine, OutputBuffer outputBuffer, PlanFragment fragment, List<TaskSource> sources)
                localExecutionPlan = LocalExecutionPlaner.plan( taskContext, fragment.getRoot(), fragment.getSymbols(), fragment.getPartitioningScheme(), fragment.getPipelineExecutionStrategy() == GROUPED_EXECUTION, fragment.getPartitionedSources(), outputBuffer);
                    PhysicalOperation physicalOperation = plan.accept(new Visitor(session, planGrouped), context); //physicalOperation这个持有OperatorFactory
                    context.addDriverFactory(); //将physicalOperation放到DriveFactory,其实DriveFactory也就是在localExecutionPlan中
                SqlTaskExecutionFactory.createSqlTaskExecution( taskStateMachine, taskContext, outputBuffer, sources, localExecutionPlan, taskExecutor, taskNotificationExecutor, queryMonitor); //physicalOperation在其中localExecutionPlan的DriveFactory中
                    SqlTaskExecution.createDriver(DriverContext driverContext, @Nullable ScheduledSplit partitionedSplit)   // 将physicalOperation放到了driverContext中
                        new SqlTaskExecution() //重要重要
                            taskExecutor.addTask() //添加task
 
*********************************  Task执行    *********************************************************
TaskExecutor.start() //@PostConstruct 
    ExecutorService.execute()
        TaskRunner.run()
            PrioritizedSplitRunner.process() // 
                DriverSplitRunner.processFor() // DriverSplitRunner是SqlTaskExe内部类
                    DriverSplitRunnerFactory.createDriver()
                    Driver.processFor()
                        Driver.processInternal()
                            Driver.processNewSources()
                            Operator.getOutput(); // 会根据不同的 sql 操作,得到不同的 Operator 实现类.然后根据实现,调用对应的 connector . 该方法返回的是一个 Page, Page 相当于一张 RDBMS 的表,只不过 Page 是列存储的. 获取 page 的时候,会根据 [Block 类型,文件格式]等,使用相应的 Loader 来 load 取数据. 
                             Operator.addInput(); //下一个 Operator 如果需要上一个 Operator 的输出,则会调用该方法
 

SetThreadName: 是 presto 模块开始执行的标志,如:

 

- try (SetThreadName ignored = new SetThreadName("Query-%s", queryStateMachine.getQueryId())) {

 

- try (SetThreadName ignored = new SetThreadName("Task-%s", taskId)) {

 

- try (SetThreadName runnerName = new SetThreadName("SplitRunner-%s", runnerId)) {

 

- try (SetThreadName ignored = new SetThreadName("HttpRemoteTask-%s", taskId)) {

2.2.3 问题说明
PlanRoot plan = analyzeQuery(); //生成了逻辑计划

planDistribution(plan); // 划分stage准则,建立task,task分发策略?

 

 

 

逻辑计划的层次如下类结构图所示

然后通过 plan方法处理得到一个Stage执行计划集合,最上层是outputStage(StageExecutionPlan),每个StageExecutionPlan内部持有

List<StageExecutionPlan>。StageExecutionPlan的这种层级结构和前面的逻辑计划的层次非常类似,但是是否一一对应没有确定。

 

sqlQueryExecution

scheduler = new SqlQueryScheduler;

scheduler.start();

 

SqlQueryScheduler:

new:

createStages 创建了5个stage(sqlStageExecution),

put 同时将对应的stage封装在对应的(根据stage的类型决定schedule的类型)StageScheduler中

start():

StageScheduler.schedule();

 

NodeScheduler.createNodeSelector

NodeSelector内部维护了NodeMap,存了active节点。

 

 

2.3 重要概念结构
2.3.1 plan、Frag及Node结构


############################################################################################################

LogicalPlanner.plan()

PlanNode root = planStatement(analysis, analysis.getStatement());

这一步的解析出来的Plan只是初步的单纯SQL层面的节点树。没有Exchange信息。在下面的这个迭代使用优化器的过程中,对节点进行了分析,在对应的位置补上了ExchangeNode,这个过程其实也就决定了Fragment的划分,进一步决定了后面stage的划分。

for (PlanOptimizer optimizer : planOptimizers) {
                root = optimizer.optimize(root, session, symbolAllocator.getTypes(), symbolAllocator, idAllocator);
                requireNonNull(root, format("%s returned a null plan", optimizer.getClass().getName()));
            }
具体的是按照什么规则去决定了哪个位置添加ExchangeNode????

 

############################################################################################################

综上:总体的plan fragment的内部node结构如下:

 

---mt官方博客的node图---->

 本例示意图

上述示意图对应的SQL: select nationkey,sum(totalprice) from orders a left outer join customer b on a.custkey=b.custkey where nationkey > 15 group by nationkey;

planRoot代表整个逻辑计划;

SubPlan代表逻辑计划树;subPlan是由Fragment和subPlan构成的。因此这条链条上其实都是Fragment。Fragment才是逻辑执行计划的核心,SubPlan是一种逻辑上的一种划分。

PlanFragment代表一串planNode节点(一般是嵌套节点);每个subPlan都有一个PlanFrag,因此Fragment其实代表这这个subPlan 。所以mt博客上的说的subplan的partition属性和我们现在的frag中的partition的属性是一致的。Fragment代表这subPlan,本例中有五个Fragment,对应着后面五个Stage(三类,具体见下文重要概念部分),因此stage其实这建立Fragment的时候就已经确定了。

planNode:单纯的SQL解析后的节点,单纯是指仅仅是SQL节点,连字段类型这种meta信息都不包括,这种meta信息在Fragment中提供了。上图和mt官方博客的subPlan划分在 aggr partial部分,project和Aggr node的先后顺序不一样。

 

2.3.2 Stage及StageScheduler


五个sqlStageExecutionsqlStage对应着前面的五个Fragment,Execution中的stageMachine持有的Fragment。

sqlStageExecutionsqlStage到底意味着什么?fragment相对于planNode多了meta信息,sqlStageExecutionsqlStage和Frag存在对应关系,那么sqlStageExecutionsqlStage在Frag基础了有多了什么?

location?memory?task?

stege如何创建task?task如何调度

DistributedExecutionPlanner.doPlan()     //递归调用doPlan方法,把之前的树形结构的Plan取出单个Plan加入到dependencies中
 
return new StageExecutionPlan(currentFragment, splitSources, dependencies.build());  //构造树形的StageExecutionPlan,StageExecutionPlan是由Fragment和子StageExecutionPlan构成的,可以看出这个StageExecutionPlan和SubPlan的结构基本是一样的。
 

 

//createStages方法的方法体,递归调用CreateStages;简单来说就是遍历节点数把节点上的所有的Fragment都取出来,创建SqlStageExecution。即一个Frag对应一个SqlStageExecution
//在创建SqlStageExecution的同时,也把SqlStageExecution放到schedulers中,后面在schedule中启动。    
new SqlStageExecution
for (StageExecutionPlan subStagePlan : plan.getSubStages()) {
            List<SqlStageExecution> subTree = createStages();
            stages.addAll(subTree);
            SqlStageExecution childStage = subTree.get(0);
            childStagesBuilder.add(childStage);
        }
如下图所示,在new的构造函数中,执行了SqlStageExecution的scheduleTask,在这个方法使用工厂类创建了RemoteTask,并start启动,同时将这个remoteTask返回。

 

2.3.3 Task


如上图所示,我们总共生成了和Fragment一一对应的5个RemoteTask。除了带有session、Fragment信息,还确定了执行节点。

PhysicalOperation physicalOperation = plan.accept(new Visitor(session, planGrouped), context);

在LocalExecutionPlanner中根据Frag通过Visitor遍历生成Operator。但是为何这个只执行了ScanFilterAndPro的生成。???

 

2.3.4 Driver


Driver代表着对一个split的处理,讲道理这个Driver的创建代码应该和split有所体现。

其次Driver上游的Task的内容还是Fragment,这里如何将Fragment转换为Operator,也应该有所体现。

debug 本次有46Driver

最开始主要是这种Operator,

Note:最复杂的的这个最后一个仅有一个

#############

LocalExchangeSourceOperator的输出Page 并不是sourceOperator,

source只有两个

2.3.5 Operator结构


2.3.6 page结构


 

2.6 代码相关框架
airlift/airlift restful服务

Guice类似spring的轻量级框架

内存管理 https://github.com/airlift/slice

 

3 工程debug
A:编译

源码拉下来之后

build方案1 ./mvnw clean install 执行的时候出现问题

build方案2 ./mvnw clean install -DskipTests

B: 跨数据源测试

tpch及hive

tpch自带,安装一个hive,执行跨数据源测试。

9 参考资料
【】https://github.com/prestodb/presto

【】https://tech.meituan.com/presto.html

【】https://blog.csdn.net/lnho2015/article/details/78628433?locationnum=9&fps=1

【Operator】https://blog.csdn.net/sinat_27545249/article/details/52450689

【示例】https://cloud.tencent.com/developer/article/1032986

转载于:https://my.oschina.net/hblt147/blog/3009971

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值