一:FLINK的链化优势
Flink 中的每个算子都可以设置并行度,每个算子的一个并行度实例就是一个 subTask。由于 Flink 的 TaskManager 运行 Task 的时候是每个 Task 采用一个单独的线程,这会带来很多线程切换和数据交换的开销,进而影响吞吐量。为了避免数据在网络或线程之间传输导致的开销,Flink 会在 JobGraph 阶段,将代码中可以优化的算子优化成一个算子链(Operator Chains)以放到一个 Task 中执行。
二:链化的API层操作
可以在API层全局禁用链化,其操作如下:
evn.disableOperatorChaining();
也可以在算子中禁用部分算子链化,其操作如下:
dataStream.keyBy(0).filter().map().disableChaining()
三:FLINK的链化源码分析
大家都知道FLINK的执行图,是由StreamGrap---> JobGrap,其链化优化环节就在StreamingJobGraphGenerator类中生成 JobGrap方法中,其源码实现如下:
private JobGraph createJobGraph() {
//链化优化方法
this.setChaining(hashes, legacyHashes);
this.setPhysicalEdges();
this.setSlotSharingAndCoLocation();
}
如上面源码可以直接看出,链化的优化在createJobGraph()方法中,其实现的主要方法在 this.setChaining(hashes, legacyHashes)中,其跟踪下去,其方法源码如下:
private void setChaining(Map<Integer, byte[]> hashes, List<Map<Integer, byte[]>> legacyHashes) {
Iterator var3 = this.streamGraph.getSourceIDs().iterator();
//循环source节点
while(var3.hasNext()) {
Integer sourceNodeId = (Integer)var3.next();
//创建链化
this.createChain(sourceNodeId, 0, new StreamingJobGraphGenerator.OperatorChainInfo(sourceNodeId, hashes, legacyHashes, this.streamGraph, (SyntheticClass_1)null));
}
}
如上源码中可以看出,其从streamGraph中,循环其source节点,每个source节点创建链化优化,其创建代码在this.createChain(sourceNodeId, 0, new StreamingJobGraphGenerator.OperatorChainInfo(sourceNodeId, hashes, legacyHashes, this.streamGraph, (SyntheticClass_1)null));中,其会把streamGraph 图当做方法参数传递下去,下面我们再看一下下一步 createChain()方法的源码,其实现如下:
//此源码只列出关键步骤
private List<StreamEdge> createChain(Integer currentNodeId, int chainIndex, StreamingJobGraphGenerator.OperatorChainInfo chainInfo) {
Integer startNodeId = chainInfo.getStartNodeId();
if (this.builtVertices.contains(startNodeId)) {
return new ArrayList();
} else {
//可以链化的输出边界
List<StreamEdge> chainableOutputs = new ArrayList();
StreamNode currentNode = this.streamGraph.getStreamNode(currentNodeId);
Iterator var9 = currentNode.getOutEdges().iterator();
StreamEdge nonChainable;
while(var9.hasNext()) {
nonChainable = (StreamEdge)var9.next();
//判断是否可以链化
if (isChainable(nonChainable, this.streamGraph)) {
//若可以就放在可以链化的队列中
chainableOutputs.add(nonChainable);
} else {
nonChainableOutputs.add(nonChainable);
}
}
var9 = chainableOutputs.iterator();
while(var9.hasNext()) {
nonChainable = (StreamEdge)var9.next();
transitiveOutEdges.addAll(this.createChain(nonChainable.getTargetId(), chainIndex + 1, chainInfo));
}
var9 = nonChainableOutputs.iterator();
}
如上源码所示,此方法是链化的核心方法,它会把streamGrap的图的输出边界进行循坏判断,是否可以链化,若可以链化就放在链化队列中,不能则放在不能链化队列中;其中isChainable(nonChainable, this.streamGraph)这个方法是对链化进行判断的核心方法,也是本节的重点要讲解的方法,我们跟进源码,其实现如下所示
public static boolean isChainable(StreamEdge edge, StreamGraph streamGraph) {
StreamNode upStreamVertex = streamGraph.getSourceVertex(edge);
StreamNode downStreamVertex = streamGraph.getTargetVertex(edge);
//是否可以链化的条件判断都在这
return
downStreamVertex.getInEdges().size() == 1 &&
upStreamVertex.isSameSlotSharingGroup(downStreamVertex) &&
areOperatorsChainable(upStreamVertex, downStreamVertex, streamGraph) &&
edge.getPartitioner() instanceof ForwardPartitioner &&
edge.getShuffleMode() != ShuffleMode.BATCH &&
upStreamVertex.getParallelism() == downStreamVertex.getParallelism() &&
streamGraph.isChainingEnabled();
}
如上面源码可以看出,是否可以链化要满足七个条件,其条件如下:
1:downStreamVertex.getInEdges().size() == 1 ,下游的算子的入度是否为1;简单的可以理解为必须只能接收上游一个算子的数据
2:upStreamVertex.isSameSlotSharingGroup(downStreamVertex) 是否共享了slot
3:areOperatorsChainable(upStreamVertex, downStreamVertex, streamGraph) 算子是否可以链化
4:edge.getPartitioner() instanceof ForwardPartitioner 算子边界的分区是否为Forward 模式,对于Partitioner,下一章再详解
5:edge.getShuffleMode() != ShuffleMode.BATCH
6:upStreamVertex.getParallelism() == downStreamVertex.getParallelism() 并行度一致
7:streamGraph.isChainingEnabled();
以上就是是否可以链化的条件判断。