presto整体流程及重要概念

 

1 Presto概览

1.1 presto设计思想及特点

多数据源:且支持扩展

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

支持标准SQL:

pipeLine设计:

这个pipeLine如何理解???

 

1.2 基础架构及执行过程

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

image.png

image.png

 

横向的一条代表:不同阶段的任务, 下面的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 工程结构

image.png

模块说明:

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结构:

image.png

image.png

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

 

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结构

image.pngimage.pngimage.png

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

LogicalPlanner.plan()

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

image.png

这一步的解析出来的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结构如下:

 

image4.jpg---mt官方博客的node图---->image.png

draw.io 本例示意图

上述示意图对应的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

image.png

draw.io

image.pngimage.png

image.png

五个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返回。

 

image.png

2.3.3 Task

image.png

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

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

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

 

2.3.4 Driver

image.png

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

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

debug 本次有46Driver

最开始主要是这种Operator,

image.png

image.png

image.png

image.png

image.png

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

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

LocalExchangeSourceOperator的输出Page 并不是sourceOperator,

source只有两个

image.png

image.png

image.png

2.3.5 Operator结构

image.png

2.3.6 page结构

image.png

 

2.6 代码相关框架

  1. airlift/airlift restful服务

  2. Guice类似spring的轻量级框架

  3. 内存管理 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章