接上节flink调用用户代码的main方法后,用户代码中一般会有如下获取flink环境的代码
val env = StreamExecutionEnvironment.getExecutionEnvironment
这里获取的环境再命令行客户的执行这个方法是就已经初始化好了,这个env里面封装了一些用户环境配置、streaming执行配置等。
//org/apache/flink/client/ClientUtils.java:66
public static void executeProgram(
PipelineExecutorServiceLoader executorServiceLoader,
Configuration configuration,
PackagedProgram program,
boolean enforceSingleJobExecution,
boolean suppressSysout)
throws ProgramInvocationException {
checkNotNull(executorServiceLoader);
final ClassLoader userCodeClassLoader = program.getUserCodeClassLoader();
final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(userCodeClassLoader);
LOG.info(
"Starting program (detached: {})",
!configuration.getBoolean(DeploymentOptions.ATTACHED));
ContextEnvironment.setAsContext(
executorServiceLoader,
configuration,
userCodeClassLoader,
enforceSingleJobExecution,
suppressSysout);
StreamContextEnvironment.setAsContext(
executorServiceLoader,
configuration,
userCodeClassLoader,
enforceSingleJobExecution,
suppressSysout);
try {
program.invokeInteractiveModeForExecution();
} finally {
ContextEnvironment.unsetAsContext();
StreamContextEnvironment.unsetAsContext();
}
} finally {
Thread.currentThread().setContextClassLoader(contextClassLoader);
}
}
DataStream是flink关于streaming的最核心抽象,它是从StreamExecutionEnvironment的addSource方法和fromSource两个方法生成。其中fromSource是新的api,下面分别进行介绍。
addSource方法会触发以下这段逻辑:
//org/apache/flink/streaming/api/environment/StreamExecutionEnvironment.java:1933
private DataStreamSource addSource(
final SourceFunction function,
final String sourceName,
@Nullable final TypeInformation typeInfo,
final Boundedness boundedness) {
checkNotNull(function);
checkNotNull(sourceName);
checkNotNull(boundedness);
TypeInformation resolvedTypeInfo =
getTypeInfo(function, sourceName, SourceFunction.class, typeInfo);
boolean isParallel = function instanceof ParallelSourceFunction;
clean(function);
final StreamSource sourceOperator = new StreamSource<>(function);
return new DataStreamSource<>(
this, resolvedTypeInfo, sourceOperator, isParallel, sourceName, boundedness);
}
这里输入的SourceFunction就是用户代码自定义实现或调用数据源函数,如env.addSource(new FlinkKafkaConsumer11)
总结上述方法逻辑:
其中用户的sourcefunction是封装在了AbstractUdfStreamOperator中
//org/apache/flink/streaming/api/operators/AbstractUdfStreamOperator.java:54
/** The user function. */
protected final F userFunction;
public AbstractUdfStreamOperator(F userFunction) {
this.userFunction = requireNonNull(userFunction);
checkUdfCheckpointingPreconditions();
}
//org/apache/flink/streaming/api/datastream/DataStreamSource.java:57
/** The constructor used to create legacy sources. */
public DataStreamSource(
StreamExecutionEnvironment environment,
TypeInformation outTypeInfo,
StreamSource operator,
boolean isParallel,
String sourceName,
Boundedness boundedness) {
super(
environment,
new LegacySourceTransformation<>(
sourceName,
operator,
outTypeInfo,
environment.getParallelism(),
boundedness));
this.isParallel = isParallel;
if (!isParallel) {
setParallelism(1);
}
}
//org/apache/flink/streaming/api/datastream/DataStream.java:129
protected final StreamExecutionEnvironment environment;
protected final Transformation transformation;
/**
* Create a new {@link DataStream} in the given execution environment with partitioning set to
* forward by default.
*
* @param environment The StreamExecutionEnvironment
*/
public DataStream(StreamExecutionEnvironment environment, Transformation transformation) {
this.environment =
Preconditions.checkNotNull(environment, "Execution Environment must not be null.");
this.transformation =
Preconditions.checkNotNull(
transformation, "Stream Transformation must not be null.");
}
freomSource方法会触发以下这段逻辑:
//org/apache/flink/streaming/api/environment/StreamExecutionEnvironment.java:1999
public DataStreamSource fromSource(
Source source,
WatermarkStrategy timestampsAndWatermarks,
String sourceName,
TypeInformation typeInfo) {
final TypeInformation resolvedTypeInfo =
getTypeInfo(source, sourceName, Source.class, typeInfo);
return new DataStreamSource<>(
this,
checkNotNull(source, "source"),
checkNotNull(timestampsAndWatermarks, "timestampsAndWatermarks"),
checkNotNull(resolvedTypeInfo),
checkNotNull(sourceName));
}
fromSource是flink新的数据源生成方法,这里用户生成Souece和原来的SourceFunction不一样,走的是另外一套继承接口。
总结上述逻辑:
上一步从数据源中获取DataStram的抽象DataStreamSource后,后面所有的转换都会在DataStram上进行。
注意到StreamExecutionEnvironment在初始化的时候会构建一个List
//org/apache/flink/streaming/api/environment/StreamExecutionEnvironment.java:190
protected final List> transformations = new ArrayList<>();
每一次的DataStream流转换操作都会把当前的Transformation添加到算子列表 transformations 中(只有 转换 transform 转换操作才会添加算子,其它都只是暂时做了 transformation 的叠加封装)
下面以map函数 为例来介绍一次流转换操作。
//org/apache/flink/streaming/api/datastream/DataStream.java:592
public SingleOutputStreamOperator map(
MapFunction mapper, TypeInformation outputType) {
return transform("Map", outputType, new StreamMap<>(clean(mapper)));
}
//org/apache/flink/streaming/api/datastream/DataStream.java:1180
public SingleOutputStreamOperator transform(
String operatorName,
TypeInformation outTypeInfo,
OneInputStreamOperatorFactory operatorFactory) {
return doTransform(operatorName, outTypeInfo, operatorFactory);
}
//org/apache/flink/streaming/api/datastream/DataStream.java:1188
protected SingleOutputStreamOperator doTransform(
String operatorName,
TypeInformation outTypeInfo,
StreamOperatorFactory operatorFactory) {
// read the output type of the input Transform to coax out errors about MissingTypeInfo
transformation.getOutputType();
OneInputTransformation resultTransform =
new OneInputTransformation<>(
this.transformation,
operatorName,
operatorFactory,
outTypeInfo,
environment.getParallelism());
@SuppressWarnings({"unchecked", "rawtypes"})
SingleOutputStreamOperator returnStream =
new SingleOutputStreamOperator(environment, resultTransform);
getExecutionEnvironment().addOperator(resultTransform);
return returnStream;
}
//org/apache/flink/streaming/api/environment/StreamExecutionEnvironment.java:2325
public void addOperator(Transformation> transformation) {
Preconditions.checkNotNull(transformation, "transformation must not be null.");
this.transformations.add(transformation);
}
用户执行env.execute()后会触发以下逻辑:
//org/apache/flink/streaming/api/environment/StreamExecutionEnvironment.java:2041
public JobExecutionResult execute(String jobName) throws Exception {
final List> originalTransformations = new ArrayList<>(transformations);
StreamGraph streamGraph = getStreamGraph();
if (jobName != null) {
streamGraph.setJobName(jobName);
}
try {
return execute(streamGraph);
} catch (Throwable t) {
Optional clusterDatasetCorruptedException =
ExceptionUtils.findThrowable(t, ClusterDatasetCorruptedException.class);
if (!clusterDatasetCorruptedException.isPresent()) {
throw t;
}
// Retry without cache if it is caused by corrupted cluster dataset.
invalidateCacheTransformations(originalTransformations);
streamGraph = getStreamGraph(originalTransformations);
return execute(streamGraph);
}
}
这段代码主要调用getStreamGraph生成StreamGraph。
//org/apache/flink/streaming/api/environment/StreamExecutionEnvironment.java:2237
private StreamGraph getStreamGraph(List> transformations) {
synchronizeClusterDatasetStatus();
return getStreamGraphGenerator(transformations).generate();
}
这里可以看到先生成了一个StreamGraphGenerator,再调用它的generate方法生成StreamGraph。主要是把transformations、执行配置、检查点配置和一些缓存信息作为成员变量封装了进去。具体的生成StreamGraph逻辑是下面这段代码:
//org/apache/flink/streaming/api/graph/StreamGraphGenerator.java:308
public StreamGraph generate() {
streamGraph = new StreamGraph(executionConfig, checkpointConfig, savepointRestoreSettings);
streamGraph.setEnableCheckpointsAfterTasksFinish(
configuration.get(
ExecutionCheckpointingOptions.ENABLE_CHECKPOINTS_AFTER_TASKS_FINISH));
shouldExecuteInBatchMode = shouldExecuteInBatchMode();
configureStreamGraph(streamGraph);
alreadyTransformed = new IdentityHashMap<>();
for (Transformation> transformation : transformations) {
transform(transformation);
}
streamGraph.setSlotSharingGroupResource(slotSharingGroupResources);
setFineGrainedGlobalStreamExchangeMode(streamGraph);
for (StreamNode node : streamGraph.getStreamNodes()) {
if (node.getInEdges().stream().anyMatch(this::shouldDisableUnalignedCheckpointing)) {
for (StreamEdge edge : node.getInEdges()) {
edge.setSupportsUnalignedCheckpoints(false);
}
}
}
final StreamGraph builtStreamGraph = streamGraph;
alreadyTransformed.clear();
alreadyTransformed = null;
streamGraph = null;
return builtStreamGraph;
}
总结上述代码逻辑:
//org/apache/flink/streaming/api/graph/StreamGraphGenerator.java:187
static {
@SuppressWarnings("rawtypes")
Map, TransformationTranslator, ? extends Transformation>>
tmp = new HashMap<>();
tmp.put(OneInputTransformation.class, new OneInputTransformationTranslator<>());
tmp.put(TwoInputTransformation.class, new TwoInputTransformationTranslator<>());
tmp.put(MultipleInputTransformation.class, new MultiInputTransformationTranslator<>());
tmp.put(KeyedMultipleInputTransformation.class, new MultiInputTransformationTranslator<>());
tmp.put(SourceTransformation.class, new SourceTransformationTranslator<>());
tmp.put(SinkTransformation.class, new SinkTransformationTranslator<>());
tmp.put(LegacySinkTransformation.class, new LegacySinkTransformationTranslator<>());
tmp.put(LegacySourceTransformation.class, new LegacySourceTransformationTranslator<>());
tmp.put(UnionTransformation.class, new UnionTransformationTranslator<>());
tmp.put(PartitionTransformation.class, new PartitionTransformationTranslator<>());
tmp.put(SideOutputTransformation.class, new SideOutputTransformationTranslator<>());
tmp.put(ReduceTransformation.class, new ReduceTransformationTranslator<>());
tmp.put(
TimestampsAndWatermarksTransformation.class,
new TimestampsAndWatermarksTransformationTranslator<>());
tmp.put(BroadcastStateTransformation.class, new BroadcastStateTransformationTranslator<>());
tmp.put(
KeyedBroadcastStateTransformation.class,
new KeyedBroadcastStateTransformationTranslator<>());
tmp.put(CacheTransformation.class, new CacheTransformationTranslator<>());
translatorMap = Collections.unmodifiableMap(tmp);
}
//org/apache/flink/streaming/runtime/translators/OneInputTransformationTranslator.java:63
@Override
public Collection translateForStreamingInternal(
final OneInputTransformation transformation, final Context context) {
return translateInternal(
transformation,
transformation.getOperatorFactory(),
transformation.getInputType(),
transformation.getStateKeySelector(),
transformation.getStateKeyType(),
context);
}
//org/apache/flink/streaming/api/graph/StreamGraph.java:511
protected StreamNode addNode(
Integer vertexID,
@Nullable String slotSharingGroup,
@Nullable String coLocationGroup,
Class extends TaskInvokable> vertexClass,
StreamOperatorFactory> operatorFactory,
String operatorName) {
if (streamNodes.containsKey(vertexID)) {
throw new RuntimeException("Duplicate vertexID " + vertexID);
}
StreamNode vertex =
new StreamNode(
vertexID,
slotSharingGroup,
coLocationGroup,
operatorFactory,
operatorName,
vertexClass);
streamNodes.put(vertexID, vertex);
return vertex;
}
生成好的StreamGraph会再次根据算子链做优化
//org/apache/flink/streaming/api/environment/StreamExecutionEnvironment.java:2183
public JobClient executeAsync(StreamGraph streamGraph) throws Exception {
checkNotNull(streamGraph, "StreamGraph cannot be null.");
final PipelineExecutor executor = getPipelineExecutor();
CompletableFuture jobClientFuture =
executor.execute(streamGraph, configuration, userClassloader);
try {
JobClient jobClient = jobClientFuture.get();
jobListeners.forEach(jobListener -> jobListener.onJobSubmitted(jobClient, null));
collectIterators.forEach(iterator -> iterator.setJobClient(jobClient));
collectIterators.clear();
return jobClient;
} catch (ExecutionException executionException) {
final Throwable strippedException =
ExceptionUtils.stripExecutionException(executionException);
jobListeners.forEach(
jobListener -> jobListener.onJobSubmitted(null, strippedException));
throw new FlinkException(
String.format("Failed to execute job '%s'.", streamGraph.getJobName()),
strippedException);
}
}
yarn job调用的是下面这个方法
//org/apache/flink/client/deployment/executors/AbstractJobClusterExecutor.java:66
@Override
public CompletableFuture execute(
@Nonnull final Pipeline pipeline,
@Nonnull final Configuration configuration,
@Nonnull final ClassLoader userCodeClassloader)
throws Exception {
final JobGraph jobGraph = PipelineExecutorUtils.getJobGraph(pipeline, configuration);
try (final ClusterDescriptor clusterDescriptor =
clusterClientFactory.createClusterDescriptor(configuration)) {
final ExecutionConfigAccessor configAccessor =
ExecutionConfigAccessor.fromConfiguration(configuration);
final ClusterSpecification clusterSpecification =
clusterClientFactory.getClusterSpecification(configuration);
final ClusterClientProvider clusterClientProvider =
clusterDescriptor.deployJobCluster(
clusterSpecification, jobGraph, configAccessor.getDetachedMode());
LOG.info("Job has been submitted with JobID " + jobGraph.getJobID());
return CompletableFuture.completedFuture(
new ClusterClientJobClientAdapter<>(
clusterClientProvider, jobGraph.getJobID(), userCodeClassloader));
}
}
StreamGraph优化生成JobGraph
//org/apache/flink/streaming/api/graph/StreamingJobGraphGenerator.java:204
private JobGraph createJobGraph() {
preValidate();
jobGraph.setJobType(streamGraph.getJobType());
jobGraph.enableApproximateLocalRecovery(
streamGraph.getCheckpointConfig().isApproximateLocalRecoveryEnabled());
// Generate deterministic hashes for the nodes in order to identify them across
// submission iff they didn't change.
Map hashes =
defaultStreamGraphHasher.traverseStreamGraphAndGenerateHashes(streamGraph);
// Generate legacy version hashes for backwards compatibility
List
核心生成逻辑是line222 setChaining中调用createChain,具体核心逻辑如下:
//org/apache/flink/streaming/api/graph/StreamingJobGraphGenerator.java:596
private List createChain(
final Integer currentNodeId,
final int chainIndex,
final OperatorChainInfo chainInfo,
final Map chainEntryPoints) {
Integer startNodeId = chainInfo.getStartNodeId();
if (!builtVertices.contains(startNodeId)) {
List transitiveOutEdges = new ArrayList();
List chainableOutputs = new ArrayList();
List nonChainableOutputs = new ArrayList();
StreamNode currentNode = streamGraph.getStreamNode(currentNodeId);
for (StreamEdge outEdge : currentNode.getOutEdges()) {
if (isChainable(outEdge, streamGraph)) {
chainableOutputs.add(outEdge);
} else {
nonChainableOutputs.add(outEdge);
}
}
for (StreamEdge chainable : chainableOutputs) {
transitiveOutEdges.addAll(
createChain(
chainable.getTargetId(),
chainIndex + 1,
chainInfo,
chainEntryPoints));
}
for (StreamEdge nonChainable : nonChainableOutputs) {
transitiveOutEdges.add(nonChainable);
createChain(
nonChainable.getTargetId(),
1, // operators start at position 1 because 0 is for chained source inputs
chainEntryPoints.computeIfAbsent(
nonChainable.getTargetId(),
(k) -> chainInfo.newChain(nonChainable.getTargetId())),
chainEntryPoints);
}
chainedNames.put(
currentNodeId,
createChainedName(
currentNodeId,
chainableOutputs,
Optional.ofNullable(chainEntryPoints.get(currentNodeId))));
chainedMinResources.put(
currentNodeId, createChainedMinResources(currentNodeId, chainableOutputs));
chainedPreferredResources.put(
currentNodeId,
createChainedPreferredResources(currentNodeId, chainableOutputs));
OperatorID currentOperatorId =
chainInfo.addNodeToChain(
currentNodeId,
streamGraph.getStreamNode(currentNodeId).getOperatorName());
if (currentNode.getInputFormat() != null) {
getOrCreateFormatContainer(startNodeId)
.addInputFormat(currentOperatorId, currentNode.getInputFormat());
}
if (currentNode.getOutputFormat() != null) {
getOrCreateFormatContainer(startNodeId)
.addOutputFormat(currentOperatorId, currentNode.getOutputFormat());
}
StreamConfig config =
currentNodeId.equals(startNodeId)
? createJobVertex(startNodeId, chainInfo)
: new StreamConfig(new Configuration());
setVertexConfig(
currentNodeId,
config,
chainableOutputs,
nonChainableOutputs,
chainInfo.getChainedSources());
if (currentNodeId.equals(startNodeId)) {
config.setChainStart();
config.setChainIndex(chainIndex);
config.setOperatorName(streamGraph.getStreamNode(currentNodeId).getOperatorName());
LinkedHashSet transitiveOutputs = new LinkedHashSet<>();
for (StreamEdge edge : transitiveOutEdges) {
NonChainedOutput output =
opIntermediateOutputs.get(edge.getSourceId()).get(edge);
transitiveOutputs.add(output);
connect(startNodeId, edge, output);
}
config.setVertexNonChainedOutputs(new ArrayList<>(transitiveOutputs));
config.setTransitiveChainedTaskConfigs(chainedConfigs.get(startNodeId));
} else {
chainedConfigs.computeIfAbsent(
startNodeId, k -> new HashMap());
config.setChainIndex(chainIndex);
StreamNode node = streamGraph.getStreamNode(currentNodeId);
config.setOperatorName(node.getOperatorName());
chainedConfigs.get(startNodeId).put(currentNodeId, config);
}
config.setOperatorID(currentOperatorId);
if (chainableOutputs.isEmpty()) {
config.setChainEnd();
}
return transitiveOutEdges;
} else {
return new ArrayList<>();
}
}
总结上述算子链接优化逻辑:
简述JobGraph生成的流程
一个JobVertex代表一个逻辑计划的节点,也就是DAG图上的顶点。注意在代码里面,JobVertex其实被作为配置封装到了StreamConfig
那具体这个StreamConfig(JobVertex)怎么生成的:
//org/apache/flink/streaming/api/graph/StreamingJobGraphGenerator.java:770
private StreamConfig createJobVertex(Integer streamNodeId, OperatorChainInfo chainInfo) {
JobVertex jobVertex;
StreamNode streamNode = streamGraph.getStreamNode(streamNodeId);
byte[] hash = chainInfo.getHash(streamNodeId);
if (hash == null) {
throw new IllegalStateException(
"Cannot find node hash. "
+ "Did you generate them before calling this method?");
}
JobVertexID jobVertexId = new JobVertexID(hash);
List> chainedOperators =
chainInfo.getChainedOperatorHashes(streamNodeId);
List operatorIDPairs = new ArrayList<>();
if (chainedOperators != null) {
for (Tuple2 chainedOperator : chainedOperators) {
OperatorID userDefinedOperatorID =
chainedOperator.f1 == null ? null : new OperatorID(chainedOperator.f1);
operatorIDPairs.add(
OperatorIDPair.of(
new OperatorID(chainedOperator.f0), userDefinedOperatorID));
}
}
if (chainedInputOutputFormats.containsKey(streamNodeId)) {
jobVertex =
new InputOutputFormatVertex(
chainedNames.get(streamNodeId), jobVertexId, operatorIDPairs);
chainedInputOutputFormats
.get(streamNodeId)
.write(new TaskConfig(jobVertex.getConfiguration()));
} else {
jobVertex = new JobVertex(chainedNames.get(streamNodeId), jobVertexId, operatorIDPairs);
}
if (streamNode.getConsumeClusterDatasetId() != null) {
jobVertex.addIntermediateDataSetIdToConsume(streamNode.getConsumeClusterDatasetId());
}
for (OperatorCoordinator.Provider coordinatorProvider :
chainInfo.getCoordinatorProviders()) {
coordinatorSerializationFutures.add(
CompletableFuture.runAsync(
() -> {
try {
jobVertex.addOperatorCoordinator(
new SerializedValue<>(coordinatorProvider));
} catch (IOException e) {
throw new FlinkRuntimeException(
String.format(
"Coordinator Provider for node %s is not serializable.",
chainedNames.get(streamNodeId)),
e);
}
},
serializationExecutor));
}
jobVertex.setResources(
chainedMinResources.get(streamNodeId), chainedPreferredResources.get(streamNodeId));
jobVertex.setInvokableClass(streamNode.getJobVertexClass());
int parallelism = streamNode.getParallelism();
if (parallelism > 0) {
jobVertex.setParallelism(parallelism);
} else {
parallelism = jobVertex.getParallelism();
}
jobVertex.setMaxParallelism(streamNode.getMaxParallelism());
if (LOG.isDebugEnabled()) {
LOG.debug("Parallelism set: {} for {}", parallelism, streamNodeId);
}
jobVertices.put(streamNodeId, jobVertex);
builtVertices.add(streamNodeId);
jobGraph.addVertex(jobVertex);
return new StreamConfig(jobVertex.getConfiguration());
}
这里重点注意
最终JobGraoh生成完成,由客户端提交给yarn创建的集群,在jobmanager中再生成物理计划。下一节介绍flink如何向yarn申请资源,并启动jobmanager和taskmanager,部署整个任务。