2019独角兽企业重金招聘Python工程师标准>>>
Prestodb概述及性能测试
- 博客分类:
- presto系列
概述内容
(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 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
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
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
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