关键对象
VExchangeNode
:负责不同fragment的数据接收的node。DataSink
:负责不同fragment的数据发送的node(这是个基类)。VDataStreamRecvr
:在VExchangeNode
中真正的负责数据的接收。VDataStreamSender
:在DataSink
中真正的负责数据的发送,如果支持向量化则会new VDataStreamSender
,否则是new DataStreamSender
。
如果两个需要传递数据的Plan Fragment
在同一个BE节点上,那么上层的sender
会直接通过RuntimeState
对象进而找到recver
,直接在sink
节点调用exchange node
中的recver
对象的add_block
将数据直接放入exchange node
中。【如何放,是否涉及内存拷贝】
sink如何发送数据 && exchange node如何接收并合并
sink
如何发送数据给exchange node
1) 发送给local
的exchange node
这里就直接调用recvr
的add_block
方法,直接将数据放入VDataStreamRecvr::_sender_queues::_block_queue
中,
2) 通过rpc发送给remote
的exchange node
最终是将数据通过rpc,同local
一样放入了_block_queue
中,后续上层节点调用get_next
方法时会调用_sender_queues
的get batch
方法从_block_queue
中获取数据
调用链:
VDataStreamSender::Channel::send_block -> PInternalServiceImpl::transmit_block -> VDataStreamMgr::transmit_block -> VDataStreamRecvr::add_block -> VDataStreamRecvr::SenderQueue::add_block
exchange node
如何接收、存储数据
exchange node
在真实运行时,上层算子会调用exchange node
的get next
方法,传入一个空block
,通过exchange node
的get next
方法将数据通过block返回给上层算子。
1) 单个sender
发来的数据
调用_sender_queues
的get_batch
方法将数据从_block_queue
中取出,最终返回给get_next
方法的block。
2) 多个sender
发来的数据
每个remote sender
都会将数据通过rpc发送给exchange node
,首先会到达exchange node
所在be
的PInternalServiceImpl::transmit_block
方法,进而调用VDataStreamMgr::transmit_block
方法中,该方法中会根据node_id
找到对应需要接收数据的exchange node
中的recvr
,然后recvr
负责接收数据,并根据sender_id
, 将数据通过add_block
方法存入指定的_sender_queues
的_block_queue
中。
如何合并的多个数据源的数据呢?
Status VSortedRunMerger::get_next 看这里
简而言之,sink向exchange node发送数据共分为2种情况:
- 两个节点在同一个BE,local send ,这样
sender
直接调用recvr
的add block
,将数据直接放入recvr
中的指定位置
。 - 两个节点不在同一个BE,则通过rpc send,在
exchange node
所在be节点上找到exchange node
的recvr
,调用add block
将数据放入指定位置
。
上面一直在说把数据放入指定位置,这个指定位置指的是哪里呢?这里也可以分为2种情况:
前置信息:recvr
中有一个_sender_queues
对象,用于接收(add block
)、存放(_block_queue
)sink节点发送来的数据。
exchange node
只有一个数据源:_sender_queues
中只有一个SenderQueue
对象,直接用_sender_queues[0]->add_block
接收数据并保存到_sender_queues[0]->_block_queue
中。上层节点通过get next
获取数据时则会直接从_block_queue
中将数据取出,并通过入参将数据返回上层。exchange node
有多个数据源:这里可以分为两个阶段,接收数据阶段与合并阶段。(1)接收数据阶段:_sender_queues
中有多个SenderQueue
对象,VDataStreamRecvr::add_block
中会根据sink节点发送数据时携带的信息sender_id
使用_sender_queues[sender_id]->add_block
将数据放入到指定SenderQueue
的_block_queue
中(即:_sender_queues[sender_id]->_block_queue
)。(2)数据合并阶段:上层节点通过get next
获取数据时则会调用_merger->get_next
方法将数据合并后通过入参将数据返回给上层。
exchange node中负责接收数据的recvr如何创建?
在plan fragment executor的prepare方法中会调用内部算子的prepare方法,如过plan fragment由agg node + exchange node组成,则会调用agg node的prepare方法,进而通过ExecNode::prepare()方法调用其child的prepare方法,即exchange node的prepare方法,此时exchange node的recvr创建完毕,等待接收数据。
数据如何从exchange node中传输给同fragment内上层算子
在plan fragment 在执行过程中,会顶层的算子(root节点_plan)会一直调用get next方法从其子节点获取数据,如果不包含scan算子,那么最底层节点会是exchange node,也就是说上层算子是通过get next方法从exchange node获取数据,exchange node这里的流程为:
这里以单一数据源不需要合并为例
exchange_node::get_next -> recvr::get_next -> SendQueue::get_batch
这里通过SendQueue::get_batch方法,将sink节点发送放入_block_queue中的数据返回给入参Block,即完成数据的返回
在 SendQueue::get_batch 时,数据有可能还未发送到_block_queue中,这里是在SendQueue::get_batch方法中while
循环+条件变量的形式等待数据的到来。
Exchange node的创建
exchange node的创建方式,所需的参数同scan agg算子相同,都是在ExecNode::create_node
方法中进行创建的,入参为ObjectPool,TPlanNode,DescriptorTbl
- ObjectPool是用于管理new出来的对象
- TPlanNode是比较重要的,从中取出所需参数,然后创建Exchange node
- DescriptorTbl,这个具体还没理解透彻。。。但是这个对象和scan agg算子传下来的是相同的。
TPlanNode中比较重要的数据结构是TExchangeNode,这里包含了创建TExchangeNode的所需对象。
这个对象的创建是在FE中,fe/fe-core/src/main/java/org/apache/doris/planner/ExchangeNode.java的toThrift()方法中进行赋值的。
struct TExchangeNode {
// The ExchangeNode's input rows form a prefix of the output rows it produces;
// this describes the composition of that prefix
1: required list<Types.TTupleId> input_row_tuples
// For a merging exchange, the sort information.
// 当Exchange node需要接收多个sink节点发送来的数据时,需要使用该对象
2: optional TSortInfo sort_info
// This is tHe number of rows to skip before returning results
3: optional i64 offset
}
未完 待续。。。