1 所有查询基于 select nationkey,sum(totalprice) from orders a left outer join customer b on a.custkey=b.custkey group by nationkey;
2 逻辑节点
1 ScanFilterAndProjectOperator(SourceOpeator)
1.1 重要方法
不需要addInput
1.1.1 getOutput
if (split == null) {
return null; }
if (!finishing && pageSource == null && cursor == null) { //如果没有建立对数据源的连接,则建立
ConnectorPageSource source = pageSourceProvider.createPageSource(operatorContext.getSession(), split, columns);
if (source instanceof RecordPageSource) { //record类型
cursor = ((RecordPageSource) source).getCursor(); }
else {pageSource = source;}
}
if (pageSource != null) { //非record类型,没有游标,直接使用pageSource处理
return processPageSource(); }
else { //record类型,使用游标可以计算出各种内存使用情况。
return processColumnSource(); // pageBuilder构建出page
}
重要类:
ConnectorPageSourceProvider
其实现类一般连接了数据源,提供了数据源的数据的抽象映射,提供了presto对数据的查询。
FAQ
1 问:经常出现的currentOPerator.getNext 是null
答: 是因为 // only return a page if buffer is full or we are finishing
所以猜想是不停的读取数据,只有buffer满了或者finish才能真正返回page,不然就返回一个null.
2 page和block的meta信息在哪里
1.1.2 processColumnSource(游标)
该方法的代码主要分为两块:
第一部分:
if (!finishing && !yieldSignal.isSet()) {
CursorProcessorOutput output = cursorProcessor.process(operatorContext.getSession().toConnectorSession(), yieldSignal, cursor, pageBuilder); //重要
pageSourceMemoryContext.setBytes(cursor.getSystemMemoryUsage());
long bytesProcessed = cursor.getCompletedBytes() - completedBytes;
long elapsedNanos = cursor.getReadTimeNanos() - readTimeNanos;
operatorContext.recordGeneratedInput(bytesProcessed, output.getProcessedRows(), elapsedNanos);
completedBytes = cursor.getCompletedBytes();
readTimeNanos = cursor.getReadTimeNanos();
if (output.isNoMoreRows()) {
finishing = true;
mergingOutput.finish();
}
}
第二行核心代码,第二行的处理过程如下图所示:已经从数据库读出了内容,然后在这里通过writeLong之类的方法,写入了blockBuilder中。
而且值得注意的是,下图中的第三四两个方法,的cursorProcessor是函数接口,其代码实现是在LocalExecutionPlanner。
Supplier<CursorProcessor> cursorProcessor = expressionCompiler.compileCursorProcessor(translatedFilter, translatedProjections, sourceNode.getId());
Supplier<PageProcessor> pageProcessor = expressionCompiler.compilePageProcessor(translatedFilter, translatedProjections, Optional.of(context.getStageId() + "_" + planNodeId));
往下追溯,
Class<? extends CursorProcessor> cursorProcessor = cursorProcessors.getUnchecked(new CacheKey(filter, projections, uniqueKey));
this.cursorProcessors = CacheBuilder.newBuilder() .recordStats().maximumSize(1000).build(CacheLoader.from(key -> compile(key.getFilter(), key.getProjections(), new CursorProcessorCompiler(metadata), CursorProcessor.class)));
// 将class对象放入缓存,返回由 class生成对应对象的lambda表达式。
private <T> Class<? extends T> compileProcessor(RowExpression filter,List<RowExpression> projections,BodyCompiler bodyCompiler,Class<? extends T> superType){
ClassDefinition classDefinition = new ClassDefinition(a(PUBLIC, FINAL),makeClassName(superType.getSimpleName()),type(Object.class),type(superType));
CallSiteBinder callSiteBinder = new CallSiteBinder();
bodyCompiler.generateMethods(classDefinition, callSiteBinder, filter, projections);
generateToString(classDefinition,callSiteBinder,toStringHelper(classDefinition.getType().getJavaClassName()).add("filter", filter).add("projections", projections).toString());
return defineClass(classDefinition, superType, callSiteBinder.getBindings(), getClass().getClassLoader());
类和方法的定义都是在这个地方。 如上文所说,在cursorProcessor.process方法中处理了数据的write,这个process方法的定义就是在上述方法中生成的。如下图所示,生成的函数如下:
generateMethods方法生成了process方法,generateFilterMethod方法以及generateProjectMethod方法,
process函数如下:
public CursorProcessorOutput process(ConnectorSession session, DriverYieldSignal yieldSignal, RecordCursor cursor, PageBuilder pageBuilder){
int completedPositions = 0;
boolean finished = false;
while(true){ //??
if (pageBuilder.isFull() || yieldSignal.isSet())
return new CursorProcessorOutput(completedPositions, false);
if (!cursor.advanceNextPosition())
return new CursorProcessorOutput(completedPositions, true);
if((filter(cursor))){
pageBuilder.declarePosition();
this.project_xx(session, cursor, pageBuilder.getBlockBuilder(42))); //project方法
}
completedPositions++;
}
}
CursorProcessorOutput中只记录了已经处理的行的简单信息,其构造函数第二个参数就是标志是否完成process,从上述代码可以看出,在do the projection中进行数据的读取,每读取一行 completedPositions++;
从下图调用栈可以看出 process--> project_1--->writeXX方法,因此真正的数据读取是在如下代码中
ifStatement.ifTrue()
.invokeVirtual(classDefinition.getType(),
"project_" + projectionIndex,
type(void.class),
type(ConnectorSession.class),
type(RecordCursor.class),
type(BlockBuilder.class));
}
project方法如下:
publid void project(ConnectorSession session,RecordCursor cursor,BlockBuilder output){
boolean wasNull = false;
output.xxx;
if(wasNull){
output.appendNull();
}else{
writeLong(long xx);
}
}
1 debug过程中发现,price列时double却变成了long是因为jdk中的Double类的public static native long doubleToRawLongBits(double value);方法
2 processPageSource方法中的pageProcessor.process用来处理非record类型的数据源的数据。pageProcessor的处理方法也是代码生成的。
第二部分:
Page page = null;
if (!pageBuilder.isEmpty() && (finishing || pageBuilder.isFull())) {
page = pageBuilder.build();
pageBuilder.reset();
}
pageBuilderMemoryContext.setBytes(pageBuilder.getRetainedSizeInBytes());
return page;
summary:
第一部分pagebuilder中包含了扫描出来的数据,每个block以blockbuilder形式暂存,在第二部分使用使用blockbuilder构建block,生成page数据
这部分在pageBuilder中new出了page,和block,对于每个block,通过blockBuilder.build()得出block数据。
1.2 查询demo
下图扫描 用户表cust
关于扫描订单表有以下几点说明:
-
scan类中的cursor含有要扫描的表和要扫描的字段的信息
-
columns比如需要扫描两个字段,但是一般type及block一般都是有三个, 猜想除了扫描字段之外还会多扫描一个。从打印的执行计划可以看出,这个出来的一个字段是Hash值。
2 HashAggregationOperator
2.1 重要变量
List<Type> groupByTypes //分组字段的类型,因为可能有多个分组字段,所以用集合表示
List<Integer> groupByChannels //分组通道,这里说的通道即分组的字段在传入的字段的位置,用整型表示
Step step //聚合的步骤,主要有partial,即部分聚合;final,即最终聚合
List<AccumulatorFactory> accumulatorFactories //聚合器工厂,根据不同的聚合类型产生不同的聚合器
Optional<Integer> hashChannel //进行hash的通道,和分组通道类似,一般根据什么分组,就要根据什么字段组合进行hash
Iterator<Page> outputIterator //输出迭代器
2.2 执行代码
public Page getOutput() {
if (finished) {
return null;
}
// process unfinished work if one exists
if (unfinishedWork != null) {
boolean workDone = unfinishedWork.process();
aggregationBuilder.updateMemory();
if (!workDone) {
return null;
}
unfinishedWork = null;
}
if (outputPages == null) {
if (finishing) {
if (!inputProcessed && produceDefaultOutput) {
// global aggregations always generate an output row with the default aggregation output (e.g. 0 for COUNT, NULL for SUM)
finished = true;
return getGlobalAggregationOutput();
}
if (aggregationBuilder == null) {
finished = true;
return null;
}
} // only flush if we are finishing or the aggregation builder is full
if (!finishing && (aggregationBuilder == null || !aggregationBuilder.isFull())) {
return null;
}
outputPages = aggregationBuilder.buildResult();
}
if (!outputPages.process()) {
return null;
}
if (outputPages.isFinished()) {
closeAggregationBuilder();
return null;
}
return outputPages.getResult();
}
其中两个方法比较重要, 一个是return getGlobalAggregationOutput(); 该方法视step是局部聚合还是final聚合执行对应的聚合方法。
// AccumulatorCompiler类i
f (step.isOutputPartial()) {
accumulators.get(j).evaluateIntermediate(output.getBlockBuilder(channel));
} else {
accumulators.get(j).evaluateFinal(output.getBlockBuilder(channel));
}
}
计算方法是accumulators中提供的,那么accumulators是从何而来?可以看下hashAggr的调用栈。
private PhysicalOperation planGlobalAggregation(AggregationNode node, PhysicalOperation source, LocalExecutionPlanContext context)
accumulatorFactories.add(buildAccumulatorFactory(source, aggregation));
return metadata.getFunctionRegistry().getAggregateFunctionImplementation(aggregation.getSignature())。。。
return specializedAggregationCache.getUnchecked(getSpecializedFunctionKey(signature));
OperatorFactory operatorFactory = new HashAggregationOperatorFactory(accumulatorFactories);
从上图可以看出,accumulators来源于accumulatorFactories,accumulatorFactories是由buildAccumulatorFactory构造出来的。接下来可以看下buildAccumulatorFactory的实现。
其中getAggregateFunctionImplementation是重点,其实现是从缓存中取出,其放入缓存的过程如下:
key -> ((SqlAggregationFunction) key.getFunction()).specialize(key.getBoundVariables(), key.getArity(), typeManager, this);
return generateAggregation(type);
GenericAccumulatorFactoryBinder factory = AccumulatorCompiler.generateAccumulatorFactoryBinder(metadata, classLoader);
generateAccumulatorClass()
generateEvaluateFinal()
generateEvaluateIntermediate()
...
return new InternalAggregationFunction(NAME, inputTypes, intermediateType, BIGINT, true, false, factory);
小结
在查询的时候,去缓存里找相关的SqlAggrFun,(如果没有找到,就由compile类去生成SqlAggregationFunction,其实现了evaluate方法,作为AccumulatorFacoties一部分),查询执行到HashAggr的部分的时候,遍历AggrFun,调用相对应的evaluate方法完成计算。
所以可以看出,真正完成计算的方法是在如下代码中完成能的。
这两个方法的实现用反射来完成,generateEvaluateFinal/generateEvaluateIntermediate;如下面这种
BytecodeBlock body = method.getBody();
Variable thisVariable = method.getThis();
BytecodeExpression state = thisVariable.getField(stateField);
body.comment("output(state, out)");
body.append(state);
body.append(out);
body.append(invoke(callSiteBinder.bind(outputFunction), "output"));
body.ret(); }
但是从body.append(invoke(callSiteBinder.bind(outputFunction), "output")); 完全看不出该方法的细节???
另一个重要的方法是outputPages = aggregationBuilder.buildResult(); 该方法也是类似上面这种。
##############
InMemoryHashAggregationBuilder.buildResult() //遍历aggregators prepare
InMemoryHashAggregationBuilder.buildResult(consecutiveGroupIds()); // 关键代码***
Aggregator.evaluate(groupId, output); //遍历aggregators 调用evaluate
GroupedAccumulator.evaluate(); //有两个实现类,DistinctingGroupedAccumulator和OrderingGroupedAccumulator。这两个实现类的evaluate的方法都是由其GroupedAccumulator属性调用的//即该evaluate方法也是由创建DistinctingGroupedAccumulator的构造函数中的Accumulator决定的。
//构造函数中的Accumulator是由instantiateGroupedAccumulator方法搞出来的。
真正的聚合操作在getOutput
-
那么addInput完成是那些东西的聚合或者说在哪个层面的聚合。
全量上卷会调用LocalExecutionPlanner.planGlobalAggregation但是不会hashJoin了。
3 PartitionedOutputOperator
顾名思义这是一个分区操作,主要是把上一步聚合分组和聚合的结果根据分组的key(比如本例中的nationkey)进行分区,写到不同的分区文件中(类似spark的shuffle操作中shuffle write)。
该Operator重要方法是addInput:核心操作有两句
page = pagePreprocessor.apply(page);
partitionFunction.partitionPage(page);
第一句是:是对page的每一行进行处理,本例的pagePreprocessor实例是PageChannelSelector,其处理方法如下: (从下述代码没看出处理了什么???)
public Page apply(Page page) {
requireNonNull(page, "page is null");
Block[] blocks = new Block[channels.length];
for (int i = 0; i < channels.length; i++) {
int channel = channels[i];
blocks[i] = page.getBlock(channel);}
return new Page(page.getPositionCount(), blocks); }
第二句:实现page的shuffle,
int partition = partitionFunction.getPartition(partitionFunctionArgs, position);
appendRow(pageBuilders[partition], page, position);
shuffle之后序列化
List<SerializedPage> serializedPages = splitPage(pagePartition, DEFAULT_MAX_PAGE_SIZE_IN_BYTES).stream()
.map(serde::serialize)
.collect(toImmutableList());
outputBuffer.enqueue(partition, serializedPages);xxxx
另外在PartitionedOutputOperator还中还涉及slice相关;
.map(serde::serialize)
PartitionedOutputOperator:SliceOutput serializationBuffer = new DynamicSliceOutput(toIntExact((page.getSizeInBytes() + Integer.BYTES))); // block length is an int
DynamicSliceOutput:DynamicSliceOutput()
this.slice = Slices.allocate(estimatedSize);
new Slice(new byte[capacity]);
4 ExchangeOperator
exchangeOperator作为Driver的Operators的第一个操作节点,是不需要addInput的。重点方法是getOutput。
首先用exchangeClient从pageBuffer中弹出序列化后的page。进行反序列化,得到正常的page对象,然后后续Operator就正常处理page。
SerializedPage page = exchangeClient.pollPage();
if (page == null) {
return null; }
operatorContext.recordGeneratedInput(page.getSizeInBytes(), page.getPositionCount());
return serde.deserialize(page);
8 TaskOutputOperator
一般作为task的最后一个操作符,用于数据的输出;
TaskOutputOperator作为一个Driver的Operators的最后一个操作的节点,不需要getOutput。重点方法是addInput,方法重点代码如下:
首先对page进行序列化,然后放入outputBuffer的队列中。
List<SerializedPage> serializedPages = splitPage(page, DEFAULT_MAX_PAGE_SIZE_IN_BYTES).stream() .map(serde::serialize) .collect(toImmutableList()); outputBuffer.enqueue(serializedPages);
序列化
类PagesSerde是page的序列化类,使用了该类的序列化方法的有TaskOutputOperator
9 Operators
一般顺序:
0 参考资料
https://blog.csdn.net/sinat_27545249/article/details/52450689#3-filterandprojectoperator