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