.
一 .前言
{@code Transformation} 代表创建DataStream 的操作.
每个数据流都有一个底层的{@code Transformation},它是所述数据流的起源。
类似DataStream#map 这样的API操作在底层都会构建一个 {@code Transformation}s 树 .
当 stream 程序执行的时候都会将这个graph使用StreamGraphGenerator 生成一个StreamGraph
{@code Transformation}不一定对应于运行时的物理操作。
一些操作仅仅是逻辑上的概念
Source Source
+ +
| |
v v
Rebalance HashPartition
+ +
| |
| |
+------>Union<------+
+
|
v
Split
+
|
v
Select
+
v
Map
+
|
v
Sink
将在运行时生成此操作图:
Source Source
+ +
| |
| |
+------->Map<-------+
+
|
v
Sink
partitioning, union, split/select 之类的操作最终都会被编码在连接map算子的edges中.
二 .代码相关
2.1. 属性
这里定义了Transformation id ,以及相关资源的属性定义…
// 32768
// Has to be equal to StreamGraphGenerator.UPPER_BOUND_MAX_PARALLELISM
public static final int UPPER_BOUND_MAX_PARALLELISM = 1 << 15;
// 分配 唯一ID 使用
// This is used to assign a unique ID to every Transformation
protected static Integer idCounter = 0;
public static int getNewNodeId() {
idCounter++;
return idCounter;
}
protected final int id;
protected String name;
// 输出类型通过TypeInformation类封装,
// 用来生成序列化用的serializers和比较大小用的comparators,以及进行一些类型检查。
protected TypeInformation<T> outputType;
// 用于处理MissingTypeInfo。
//
// This is used to handle MissingTypeInfo.
//
// As long as the outputType has not been queried
// it can still be changed using setOutputType().
//
// Afterwards an exception is thrown when trying to change the output type.
protected boolean typeUsed;
// 并行度
private int parallelism;
/**
* The maximum parallelism for this stream transformation.
*
* It defines the upper limit for dynamic scaling and the number of key groups used for partitioned state.
*/
private int maxParallelism = -1;
/**
* The minimum resources for this stream transformation. It defines the lower limit for dynamic
* resources resize in future plan.
*/
private ResourceSpec minResources = ResourceSpec.DEFAULT;
/**
* 此stream转换的首选资源。
* 它定义了未来计划中动态资源调整的上限。
*
*
* The preferred resources for this stream transformation.
*
* It defines the upper limit for dynamic resource resize in future plan.
*/
private ResourceSpec preferredResources = ResourceSpec.DEFAULT;
/**
*
*
* Each entry in this map represents a operator scope use case that this transformation needs managed memory for.
*
* The keys indicate the use cases, while the values are the
* use-case-specific weights for this transformation.
*
*
* Managed memory reserved for a use case
* will be shared by all the declaring transformations within a slot according to this weight.
*/
private final Map<ManagedMemoryUseCase, Integer> managedMemoryOperatorScopeUseCaseWeights = new HashMap<>();
/** Slot scope use cases that this transformation needs managed memory for. */
private final Set<ManagedMemoryUseCase> managedMemorySlotScopeUseCases = new HashSet<>();
/**
* transformation 指定的 User-specified ID
* job重启时依旧使用相同的 operator ID.
* 使用内部的 静态的counter 自动生成id ,
*
* User-specified ID for this transformation. This is used to assign the same operator ID across
* job restarts.
*
* There is also the automatically generated {@link #id}, which is assigned from a static counter. That field is independent from this.
*/
private String uid;
private String userProvidedNodeHash;
// 超时相关..
protected long bufferTimeout = -1;
// slot sharing 组..
private String slotSharingGroup;
@Nullable private String coLocationGroupKey;
2.2. 构造方法
只有一个构造方法, 根据name , 输出类型, 并行度构建Transformation.
/**
* 根据name , 输出类型, 并行度构建Transformation
* Creates a new {@code Transformation} with the given name, output type and parallelism.
*
* @param name The name of the {@code Transformation}, this will be shown in Visualizations and
* the Log
* @param outputType The output type of this {@code Transformation}
* @param parallelism The parallelism of this {@code Transformation}
*/
public Transformation(String name, TypeInformation<T> outputType, int parallelism) {
this.id = getNewNodeId();
this.name = Preconditions.checkNotNull(name);
this.outputType = outputType;
this.parallelism = parallelism;
this.slotSharingGroup = null;
}
2.3. 资源相关
名称 | 含义 |
---|---|
setResources(ResourceSpec minResources, ResourceSpec preferredResources) | 为该 Transformation这是最小和首选的资源 |
declareManagedMemoryUseCaseAtOperatorScope | 声明此转换包含特定的运算符作用域托管内存用例。 |
declareManagedMemoryUseCaseAtSlotScope | 声明此转换包含特定的solt作用域托管内存用例。 |
其他的就不细说了,就是对属性的 set/get 操作…
2.4. 获取属性相关(需要子类实现)
名称 | 含义 |
---|---|
getTransitivePredecessors | 获取前置的Transformation |
getInputs | 获取输入的Transformation |
三 .实现类
Transformation 的实现类很多,所以我们先看部分一级的实现类.
实现类 | 描述 |
---|---|
PhysicalTransformation | 创建物理操作,它启用设置{@link ChainingStrategy} |
UnionTransformation | 此转换表示多个输入{@link Transformation Transformations}的并集。 这不会创建物理操作,只会影响上游操作与下游操作的连接方式。 |
PartitionTransformation | 此转换表示输入元素分区的更改。 这不会创建物理操作,只会影响上游操作与下游操作的连接方式。 |
SideOutputTransformation | 此transformation表示对具有给定{@link OutputTag}的上游操作的边输出的选择。 |
CoFeedbackTransformation | xxx |
FeedbackTransformation | xxx |
3.1. PhysicalTransformation
创建物理操作,它启用设置{@link ChainingStrategy}
PhysicalTransformation 继承Transformation抽象类, 新增了一个 setChainingStrategy
的方法, 通过该方法可以定义operator 的连接方式.
/** Sets the chaining strategy of this {@code Transformation}. */
public abstract void setChainingStrategy(ChainingStrategy strategy);
- ChainingStrategy 是一个枚举类.
定义算子的链接方案。
当一个操作符链接到前置线程时,意味着它们在同一个线程中运行。
一个operator可以包含多个步骤.
StreamOperator使用的默认值是{@link#HEAD},这意味着operator没有链接到它的前一个operator。
大多数的operators 将会以 {@link #ALWAYS} 复写. 意味着他们会尽可能的chained 前置operator。
类型 | 描述 |
---|---|
ALWAYS | 算子会尽可能的Chain在一起(为了优化性能,最好是使用最大数量的chain和加大算子的并行度) |
NEVER | 当前算子不会与前置和后置算子进行Chain |
HEAD | 当前算子允许被后置算子Chain,但不会与前置算子进行Chain |
HEAD_WITH_SOURCES | 与HEAD类似,但此策略会尝试Chain Source算子 |
3.2. UnionTransformation
UnionTransformation是Transformation的子类. 表示多个输入{@link Transformation Transformations}的并集。 这不会创建物理操作,只会影响上游操作与下游操作的连接方式。
UnionTransformation 只有一个属性 . private final List<Transformation<T>> inputs;
通过UnionTransformation的构造方法传入.
传入的时候会依次验证输入的类型和输出的类型是否一致.
/**
* 通过给定的Transformations 构建一个UnionTransformation
* Creates a new {@code UnionTransformation} from the given input {@code Transformations}.
*
* <p>The input {@code Transformations} must all have the same type.
*
* @param inputs The list of input {@code Transformations}
*/
public UnionTransformation(List<Transformation<T>> inputs) {
super("Union", inputs.get(0).getOutputType(), inputs.get(0).getParallelism());
for (Transformation<T> input : inputs) {
if (!input.getOutputType().equals(getOutputType())) {
throw new UnsupportedOperationException("Type mismatch in input " + input);
}
}
this.inputs = Lists.newArrayList(inputs);
}
实现了getInputs 方法和getTransitivePredecessors方法获取输入流…
@Override
public List<Transformation<?>> getInputs() {
return new ArrayList<>(inputs);
}
@Override
public List<Transformation<?>> getTransitivePredecessors() {
List<Transformation<?>> result = Lists.newArrayList();
result.add(this);
for (Transformation<T> input : inputs) {
result.addAll(input.getTransitivePredecessors());
}
return result;
}
3.3. PartitionTransformation
PartitionTransformation是Transformation的子类.
此转换表示输入元素分区的更改。
它不会创建物理操作,它只会影响上游操作与下游操作的连接方式。
3.3.1. 属性
PartitionTransformation 有三个属性 :
// 输入
private final Transformation<T> input;
// 分区器
private final StreamPartitioner<T> partitioner;
// ShuffleMode : 如果是流的话 应该是: PIPELINED
private final ShuffleMode shuffleMode;
3.3.2. 构造方法
根据输入和StreamPartitioner 生成一个PartitionTransformation
/**
* Creates a new {@code PartitionTransformation} from the given input and {@link
* StreamPartitioner}.
*
* @param input The input {@code Transformation}
* @param partitioner The {@code StreamPartitioner}
* @param shuffleMode The {@code ShuffleMode}
*/
public PartitionTransformation(
Transformation<T> input, StreamPartitioner<T> partitioner, ShuffleMode shuffleMode) {
super("Partition", input.getOutputType(), input.getParallelism());
this.input = input;
this.partitioner = partitioner;
this.shuffleMode = checkNotNull(shuffleMode);
}
3.3.3. 获取属性相关
剩下的就是获取分区器, ShuffleMode 以及 获取输入相关的方法了…
/**
* Returns the {@code StreamPartitioner} that must be used for partitioning the elements of the
* input {@code Transformation}.
*/
public StreamPartitioner<T> getPartitioner() {
return partitioner;
}
/** Returns the {@link ShuffleMode} of this {@link PartitionTransformation}. */
public ShuffleMode getShuffleMode() {
return shuffleMode;
}
@Override
public List<Transformation<?>> getTransitivePredecessors() {
List<Transformation<?>> result = Lists.newArrayList();
result.add(this);
result.addAll(input.getTransitivePredecessors());
return result;
}
@Override
public List<Transformation<?>> getInputs() {
return Collections.singletonList(input);
}
3.3.4. ShuffleMode
ShuffleMode 定义了operators 之间交换数据的模式.
ShuffleMode 是一个枚举类,一共有三种.
名称 | 描述 |
---|---|
PIPELINED | 生产者和消费者同时在线. 生产出的数据立即会被消费者消费… |
BATCH | 生产者先产生数据至完成&停止. 之后 消费者启动消费数据. |
UNDEFINED | shuffle mode : 未定义 , 由框架决定 shuffle mode. 框架最后将选择{@link ShuffleMode#BATCH}或{@link ShuffleMode#PIPELINED}中的一个。 |
3.3.5. StreamPartitioner
根据上图可以获得信息. StreamPartitioner实现了ChannelSelector 接口.
ChannelSelector实现了IOReadableWritable 接口.
IOReadableWritable 里面只有read和write接口.
ChannelSelector对ChannelSelector接口进行了扩展.
新增了setup/selectChannel/isBroadcast 三个方法.
3.3.5.1. IOReadableWritable 定义的方法清单 :
每个类必须自行选择他们自己的二进制序列&反序列化方式.
特别是,records 必须实现此接口,以便指定如何将其数据传输到二进制表示形式。
实现此接口时,请确保实现类具有默认(无参数)构造函数!
名称 | 描述 |
---|---|
void read(DataInputView in) throws IOException; | 从给定的数据输入视图读取对象的内部数据。 |
void write(DataOutputView out) throws IOException; | 将对象的内部数据写入给定的数据输出视图。 |
3.3.5.2. ChannelSelector 定义的方法清单 :
{@link ChannelSelector} 决定数据记录如何写入到 logical channels .
名称 | 描述 |
---|---|
setup | 设置 初始化 channel selector 的数量. |
selectChannel | 返回数据写入的 logical channel 的索引 为broadcast channel selectors 调用此方法是非法的, 在这种情况下,此方法可能无法实现(例如,通过抛出{@link UnsupportedOperationException})。 |
isBroadcast | 返回channel selector是否始终选择所有输出通道 |
3.3.5.3. StreamPartitioner 定义的清单 :
StreamPartitioner 是一个抽象类, 实现了ChannelSelector 接口 .
里面只有一个属性 protected int numberOfChannels;
通过 setup(int numberOfChannels)
方法来设置 channel的数量.
同时实现 isBroadcast
方法返回的值为 false
. 重写了 equals
, hashCode
方法 .
新增的抽象方法 copy
.
定义了 getUpstreamSubtaskStateMapper & getDownstreamSubtaskStateMapper 方法.
定义了恢复 in-flight 数据期间上游重新回放时此分区程序的行为。
/**
* Defines the behavior of this partitioner, when upstream rescaled during recovery of in-flight
* data.
*/
public SubtaskStateMapper getUpstreamSubtaskStateMapper() {
return SubtaskStateMapper.ARBITRARY;
}
/**
* Defines the behavior of this partitioner, when downstream rescaled during recovery of
* in-flight data.
*/
public abstract SubtaskStateMapper getDownstreamSubtaskStateMapper();
3.3.6. SubtaskStateMapper
SubtaskStateMapper是一个枚举类 .
{@code SubtaskStateMapper}缩小了回放期间需要读取的子任务,以便在检查点中存储了in-flight中的数据时从特定子任务恢复。
旧子任务到新子任务的映射可以是唯一的,也可以是非唯一的。
唯一分配意味着一个特定的旧子任务只分配给一个新的子任务。
非唯一分配需要向下筛选。
这意味着接收方必须交叉验证反序列化记录是否真正属于新的子任务。
大多数{@code SubtaskStateMapper}只会产生唯一的赋值,因此是最优的
一些重缩放器,比如{@link#RANGE},创建了唯一映射和非唯一映射的混合,其中下游任务需要对一些映射的子任务进行过滤。
名称 | 描述 | 分区器 |
---|---|---|
ARBITRARY | 额外的状态被重新分配到其他子任务,而没有任何特定的保证(只匹配上行和下行)。 | 超类: StreamPartitioner [getUpstreamSubtaskStateMapper] |
DISCARD_EXTRA_STATE | 丢弃额外状态。如果所有子任务都已包含相同的信息(广播),则非常有用。 | BroadcastPartitioner [getUpstreamSubtaskStateMapper] |
FIRST | 将额外的子任务还原到第一个子任务。 | GlobalPartitioner |
FULL | 将状态复制到所有子任务。这种回放会造成巨大的开销,完全依赖于对下游数据进行过滤。 | CustomPartitionerWrapper |
RANGE | 将旧范围重新映射到新范围 | KeyGroupStreamPartitioner |
ROUND_ROBIN | 以循环方式重新分配子任务状态。 | ShufflePartitioner, RebalancePartitioner, ForwardPartitioner , RescalePartitioner, BroadcastPartitioner[getDownstreamSubtaskStateMapper] |
3.4. SideOutputTransformation
SideOutputTransformation是Transformation的子类.
此transformation表示对具有给定{@link OutputTag}的上游操作的边输出的选择。
这个类有两个属性 一个输入 private final Transformation<?> input;
,一个输出 private final OutputTag<T> tag;
输入和输出的参数由SideOutputTransformation
构造方法指定.
public SideOutputTransformation(Transformation<?> input, final OutputTag<T> tag) {
super("SideOutput", tag.getTypeInfo(), requireNonNull(input).getParallelism());
this.input = input;
this.tag = requireNonNull(tag);
}
- OutputTag类型
{@link OutputTag}是一个类型化和命名的标记,用于标记操作符的边输出。
{@code OutputTag}必须始终是匿名内部类,以便Flink可以为泛型类型参数派生一个{@link TypeInformation}。
示例:
OutputTag<Tuple2<String, Long>> info = new OutputTag<Tuple2<String, Long>>(“late-data”){};
- 属性
只有两个属性,一个id , 另外一个是typeInfo.
private final String id;
private final TypeInformation<T> typeInfo;
- 构造方法
有两个构造方法
/**
* Creates a new named {@code OutputTag} with the given id.
*
* @param id The id of the created {@code OutputTag}.
*/
public OutputTag(String id) {
Preconditions.checkNotNull(id, "OutputTag id cannot be null.");
Preconditions.checkArgument(!id.isEmpty(), "OutputTag id must not be empty.");
this.id = id;
try {
this.typeInfo = TypeExtractor.createTypeInfo(this, OutputTag.class, getClass(), 0);
} catch (InvalidTypesException e) {
throw new InvalidTypesException(
"Could not determine TypeInformation for the OutputTag type. "
+ "The most common reason is forgetting to make the OutputTag an anonymous inner class. "
+ "It is also not possible to use generic type variables with OutputTags, such as 'Tuple2<A, B>'.",
e);
}
}
/**
* Creates a new named {@code OutputTag} with the given id and output {@link TypeInformation}.
*
* @param id The id of the created {@code OutputTag}.
* @param typeInfo The {@code TypeInformation} for the side output.
*/
public OutputTag(String id, TypeInformation<T> typeInfo) {
Preconditions.checkNotNull(id, "OutputTag id cannot be null.");
Preconditions.checkArgument(!id.isEmpty(), "OutputTag id must not be empty.");
this.id = id;
this.typeInfo = Preconditions.checkNotNull(typeInfo, "TypeInformation cannot be null.");
}
3.5. CoFeedbackTransformation
CoFeedbackTransformation的子类.
这表示拓扑中的反馈点。
feedback 元素不需要和 上有的{@code Transformation} 匹配.
因为仅仅允许{@code CoFeedbackTransformation}之后的操作是 {@link org.apache.flink.streaming.api.transformations.TwoInputTransformation TwoInputTransformations}.
上游{@code Transformation}将连接到Co-Transform 的第一个输入,而反馈 edges 将连接到第二个输入。
同时保留了输入边和反馈边的划分。它们也可以有不同的分区策略。
然而,这要求反馈{@code Transformation}的并行性必须与输入{@code Transformation}的并行度匹配。
上游{@code Transformation}未连接到此{@code CoFeedbackTransformation}。
而是直接连接到 {@code TwoInputTransformation} 之后的 {@code CoFeedbackTransformation}.
这与批处理中的迭代不同。
- 属性
只有两个属性
private final List<Transformation<F>> feedbackEdges;
private final Long waitTime;
- addFeedbackEdge
/**
* Adds a feedback edge. The parallelism of the {@code Transformation} must match the
* parallelism of the input {@code Transformation} of the upstream {@code Transformation}.
*
* @param transform The new feedback {@code Transformation}.
*/
public void addFeedbackEdge(Transformation<F> transform) {
if (transform.getParallelism() != this.getParallelism()) {
throw new UnsupportedOperationException(
"Parallelism of the feedback stream must match the parallelism of the original"
+ " stream. Parallelism of original stream: "
+ this.getParallelism()
+ "; parallelism of feedback stream: "
+ transform.getParallelism());
}
feedbackEdges.add(transform);
}
四 .PhysicalTransformation 子类
PhysicalTransformation 的子类有点多, 挑几个类型瞄一眼. 其他的等有空的时候再看…
4.1. OneInputTransformation
此转换表示将{@link org.apache.flink.streaming.api.operators.OneInputStreamOperator} 应用于一个输入 {@link Transformation}.
4.1.1. 属性
private final Transformation<IN> input;
private final StreamOperatorFactory<OUT> operatorFactory;
private KeySelector<IN, ?> stateKeySelector;
private TypeInformation<?> stateKeyType;
4.1.2. 构造方法
public OneInputTransformation(
Transformation<IN> input,
String name,
StreamOperatorFactory<OUT> operatorFactory,
TypeInformation<OUT> outputType,
int parallelism) {
super(name, outputType, parallelism);
this.input = input;
this.operatorFactory = operatorFactory;
}
4.2. TwoInputTransformation
两个输入一个输出操作…
/**
* This Transformation represents the application of a {@link TwoInputStreamOperator} to two input
* {@code Transformations}. The result is again only one stream.
*
* @param <IN1> The type of the elements in the first input {@code Transformation}
* @param <IN2> The type of the elements in the second input {@code Transformation}
* @param <OUT> The type of the elements that result from this {@code TwoInputTransformation}
*/
4.3. AbstractMultipleInputTransformation
AbstractMultipleInputTransformation抽象类…
/**
* Base class for transformations representing the application of a {@link
* org.apache.flink.streaming.api.operators.MultipleInputStreamOperator} to input {@code
* Transformations}. The result is again only one stream.
*
* @param <OUT> The type of the elements that result from this {@code MultipleInputTransformation}
*/
- 属性
protected final List<Transformation<?>> inputs = new ArrayList<>();
protected final StreamOperatorFactory<OUT> operatorFactory;
4.4. MultipleInputTransformation
MultipleInputTransformation 是AbstractMultipleInputTransformation 子类.
/** {@link AbstractMultipleInputTransformation} implementation for non-keyed streams. */
@Internal
public class MultipleInputTransformation<OUT> extends AbstractMultipleInputTransformation<OUT> {
public MultipleInputTransformation(
String name,
StreamOperatorFactory<OUT> operatorFactory,
TypeInformation<OUT> outputType,
int parallelism) {
super(name, operatorFactory, outputType, parallelism);
}
public MultipleInputTransformation<OUT> addInput(Transformation<?> input) {
inputs.add(input);
return this;
}
}
4.5. KeyedMultipleInputTransformation
KeyedMultipleInputTransformation 是AbstractMultipleInputTransformation 子类.
- 新增了属性
private final List<KeySelector<?, ?>> stateKeySelectors = new ArrayList<>();
protected final TypeInformation<?> stateKeyType;