多数据源:且支持扩展
计算方式:完全基于内存进行计算,并没有使用mapReduce。
支持标准SQL:
pipeLine设计:
这个pipeLine如何理解???
典型的主从架构,coordinator负责调度,worker上的进程负责接受调度,执行具体的task。每个task读入具体的split,并进行处理。
横向的一条代表:不同阶段的任务, 下面的work代表执行的实体。左边的work负责对SqlQueryExecution进行解析,输出的结果是SqlStageExecution。这个SqlStageExecution其实代表的是多个subPlan。其中有源数据读取的,也有进行汇总计算的。
然后根据各个节点的情况进行任务分发,到被分发的到节点上就是SqlTaskExecution。然后执行。
note:presto形成的逻辑执行计划进一步进行了拆分,这个拆分是为了基于内存的并发计算。
模型:
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内部并不是流式的
https://cloud.tencent.com/developer/article/1032986
模块说明:
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结构:
普通函数和窗口函数集中在此。
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() |
发起查询之后就一直获取查询状态 |
接上图:
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 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 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)) { |
PlanRoot plan = analyzeQuery(); //生成了逻辑计划
planDistribution(plan); // 划分stage准则,建立task,task分发策略?
逻辑计划的层次如下类结构图所示
然后通过 plan方法处理得到一个Stage执行计划集合,最上层是outputStage(StageExecutionPlan),每个StageExecutionPlan内部持有
List
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节点。
############################################################################################################
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的先后顺序不一样。
五个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 subTree = createStages();
stages.addAll(subTree);
SqlStageExecution childStage = subTree.get(0);
childStagesBuilder.add(childStage);
}
如下图所示,在new的构造函数中,执行了SqlStageExecution的scheduleTask,在这个方法使用工厂类创建了RemoteTask,并start启动,同时将这个remoteTask返回。
如上图所示,我们总共生成了和Fragment一一对应的5个RemoteTask。除了带有session、Fragment信息,还确定了执行节点。
PhysicalOperation physicalOperation = plan.accept(new Visitor(session, planGrouped), context);
在LocalExecutionPlanner中根据Frag通过Visitor遍历生成Operator。但是为何这个只执行了ScanFilterAndPro的生成。???
Driver代表着对一个split的处理,讲道理这个Driver的创建代码应该和split有所体现。
其次Driver上游的Task的内容还是Fragment,这里如何将Fragment转换为Operator,也应该有所体现。
debug 本次有46Driver
最开始主要是这种Operator,
‘
Note:最复杂的的这个最后一个仅有一个
#############
LocalExchangeSourceOperator的输出Page 并不是sourceOperator,
source只有两个
airlift/airlift restful服务
Guice类似spring的轻量级框架
内存管理 https://github.com/airlift/slice
A:编译
源码拉下来之后
build方案1 ./mvnw clean install 执行的时候出现问题
build方案2 ./mvnw clean install -DskipTests
B: 跨数据源测试
tpch及hive
tpch自带,安装一个hive,执行跨数据源测试。
【】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