.
- 一 .前言
- 二 .几个基本概念
- 2.1. IntermediateDataset
- 2.2. IntermediateResult 和 IntermediateResultpartition
- 2.3. ResultPartition 和 ResultSubpartition
- 2.4. InputGate 和 InputChannel
- 三 .相关Class梳理
- 3.1. ResultPartitionWriter
- 3.2. ResultPartition
- 3.2.1. 属性相关
- 3.2.2. 构造方法
- 3.2.3. 方法相关
- 3.3. BufferWritingResultPartition
- 3.3.1. 属性
- 3.3.2. 构造方法
- 3.3.3. 方法相关
- 3.4. BufferBuilder
- 3.4.1. 属性
- 3.4.2. 构造方法
- 3.4.3. 构造BufferConsumer相关方法
- 3.4.3. MemorySegment内存操作相关方法
- 3.4.3. 资源回收
- 3.5. BufferConsumer
- 3.5.1. 属性
- 3.5.2. 构造方法
- 3.5.3. 操作方法
- 3.6. ResultSubpartitionView
- 3.7. 实现类PipelinedSubpartitionView
- 3.7.1. 属性
- 3.7.2. 构造方法
- 3.7.3. 方法相关
- 3.8. PipelinedResultPartition
- 3.8.1. 属性
- 3.8.2 构造方法
- 3.8.3 方法相关
- 3.9.ResultSubpartition
- 3.9.1. 属性
- 3.9.2. ResultSubpartitionInfo
- 3.9.3. BufferAndBacklog
- 3.9.4. 方法相关
- 四. StreamRecord
- 五. Output 相关
- 5.1. Output
- 5.2. CountingOutput
- 5.2. CopyingChainingOutput
- 5.3 ChainingOutput
- 5.3.1 属性
- 5.3.2. 方法
- 5.3.3. emitWatermark
- 5.3.4. emitLatencyMarker
- 六. RecordWriter
- 6.1. 属性
- 6.2. 构造方法
- 6.3. emit 相关方法
- 6.4. broadcastEvent 相关
- 6.5. ChannelSelectorRecordWriter
- 6.5.1. 属性
- 6.5.2. ChannelSelector
- 6.5.3. 构造函数
- 6.5.4. emit 方法
- 6.5.5. broadcastEmit(T record)
一 .前言
Stream的计算模型采用的是PUSH模式, 上游主动向下游推送数据, 上下游之间采用生产者-消费者模式, 下游收到数据触发计算, 没
有数据则进入等待状态。 PUSH模式的数据处理过程也叫作Pipeline, 提到Pipeline或者流水线的时候, 一般是指PUSH模式的数据处理过程。
Task之间数据交换从本质上来说就是一个典型的生产者-消费者模型,上游算子生产数据到 ResultPartition 中,下游算子通过 InputGate 消费数据。由于不同的 Task 可能在同一个 TaskManager 中运行,也可能在不同的 TaskManager 中运行:对于前者,不同的 Task 其实就是同一个 TaskManager 进程中的不同的线程,它们的数据交换就是在本地不同线程间进行的;对于后者,必须要通过网络进行通信。
二 .几个基本概念
2.1. IntermediateDataset
IntermediateDataset
是在 JobGraph 中对中间结果的抽象。我们知道,JobGraph 是对 StreamGraph 进一步进行优化后得到的逻辑图,它尽量把可以 chain 到一起 operator 合并为一个 JobVertex,而 IntermediateDataset
就表示一个 JobVertex 的输出结果。JobVertex 的输入是 JobEdge,而 JobEdge 可以看作是 IntermediateDataset
的消费者。一个 JobVertex 也可能产生多个 IntermediateDataset
。需要说明的一点是,目前一个 IntermediateDataset
实际上只会有一个 JobEdge 作为消费者,也就是说,一个 JobVertex 的下游有多少 JobVertex 需要依赖当前节点的数据,那么当前节点就有对应数量的 IntermediateDataset
。
2.2. IntermediateResult 和 IntermediateResultpartition
在 JobManager 中,JobGraph 被进一步转换成可以被调度的并行化版本的执行图,即 ExecutionGraph。在 ExecutionGraph 中,和 JobVertex 对应的节点是 ExecutionJobVertex,和 IntermediateDataset
对应的则是 IntermediataResult
。由于一个节点在实际运行时可能有多个并行子任务同时运行,所以 ExecutionJobVertex 按照并行度的设置被拆分为多个 ExecutionVertex,每一个表示一个并行的子任务。同样的,一个 IntermediataResult
也会被拆分为多个 IntermediateResultPartition
,IntermediateResultPartition
对应 ExecutionVertex
的输出结果。
一个 IntermediateDataset
只有一个消费者,那么一个 IntermediataResult
也只会有一个消费者;
但是到了 IntermediateResultPartition
这里,由于节点被拆分成了并行化的节点,所以一个 IntermediateResultPartition
可能会有多个 ExecutionEdge
作为消费者。
2.3. ResultPartition 和 ResultSubpartition
ExecutionGraph 还是 JobManager 中用于描述作业拓扑的一种逻辑上的数据结构,其中表示并行子任务的 ExecutionVertex 会被调度到 TaskManager 中执行,一个 Task 对应一个 ExecutionVertex。同 ExecutionVertex 的输出结果 IntermediateResultPartition
相对应的则是 ResultPartition
。**IntermediateResultPartition 可能会有多个 ExecutionEdge 作为消费者,**那么在 Task 这里,ResultPartition 就会被拆分为多个 ResultSubpartition,下游每一个需要从当前 ResultPartition
消费数据的 Task 都会有一个专属的 ResultSubpartition
。
ResultPartitionType 指定了 ResultPartition 的不同属性,这些属性包括是否流水线模式、是否会产生反压以及是否限制使用的 Network buffer 的数量。ResultPartitionType 有三个枚举值:
- BLOCKING:非流水线模式,无反压,不限制使用的网络缓冲的数量
- PIPELINED:流水线模式,有反压,不限制使用的网络缓冲的数量
- PIPELINED_BOUNDED:流水线模式,有反压,限制使用的网络缓冲的数量
其中是否PIPELINED模式这个属性会对消费行为产生很大的影响:
如果是流水线模式,那么在 ResultPartition 接收到第一个 Buffer 时,消费者任务就可以进行准备消费;而如果非流水线模式,那么消费者将等到生产端任务生产完数据之后才进行消费。目前在 Stream 模式下使用的类型是 PIPELINED_BOUNDED。
2.4. InputGate 和 InputChannel
在 Task 中,InputGate
是对输入的封装,InputGate 是和 JobGraph 中 JobEdge 一一对应的。也就是说,InputGate 实际上对应的是该 Task 依赖的上游算子(包含多个并行子任务),每个 InputGate 消费了一个或多个 ResultPartition。InputGate 由 InputChannel 构成。
三 .相关Class梳理
Task 产出的每一个 ResultPartition 都有一个关联的 ResultPartitionWriter,同时也都有一个独立的 LocalBufferPool 负责提供写入数据所需的 buffer。
如上图 Flat Map 算子的输出就是PipelinedResultPartition , 我们看一下PipelinedResultPartition的相关Class
3.1. ResultPartitionWriter
Task 产出的每一个 ResultPartition 都有一个关联的 ResultPartitionWriter,同时也都有一个独立的 LocalBufferPool 负责提供写入数据所需的 buffer。
ResultPartitionWriter 是一个接口类, 定义了一系列的方法.
名称 | 描述 |
void setup() throws IOException; | 初始化相关 |
ResultPartitionID getPartitionId(); | 获取ResultPartitionID |
int getNumberOfSubpartitions(); | 获取子分区的数量 |
int getNumTargetKeyGroups(); | xxx |
void emitRecord(ByteBuffer record, int targetSubpartition) throws IOException; | 将数据发送到制定的子分区 |
void broadcastRecord(ByteBuffer record) throws IOException; | 广播数据 |
void broadcastEvent(AbstractEvent event, boolean isPriorityEvent) | 广播事件 |
void setMetricGroup(TaskIOMetricGroup metrics); | 设置metric相关 |
ResultSubpartitionView createSubpartitionView( int index, BufferAvailabilityListener availabilityListener) | 构建SubpartitionView,用于读取子分区数据 |
void flushAll(); | flush所有分区的数据 |
void flush(int subpartitionIndex); | flush某一个分区的数据 |
void fail(@Nullable Throwable throwable); | 异常相关 |
void finish() throws IOException; | 执行设置分区状态为成功 |
boolean isFinished(); | 是否成功 |
void release(Throwable cause); | 执行释放资源 |
boolean isReleased(); | 资源是否释放成功 |
void close() throws Exception; | 执行分区关闭 |
3.2. ResultPartition
ResultPartition 是一个抽象类 , 实现了ResultPartitionWriter 接口…
一个数据处理流程(Task)对应着一个ResultPartition,
ResultPartition的数据需要发送数据到多个下游Channel(对应着下游多个Task),
ResultPartition中的数据专门为下游不同的接收者做了分组,这个分组叫做ResultSubpartition。
对应JobGraph中的IntermediateResultPartition .
3.2.1. 属性相关
名称 | 描述 |
String owningTaskName | 任务名称: “Flat Map (1/4)#0 (b2490d6207a4eaa9f285fb307bd31782)” |
int partitionIndex | 0 |
ResultPartitionID partitionId | 分区ID |
ResultPartitionType partitionType | “PIPELINED_BOUNDED” |
ResultPartitionManager partitionManager | ResultPartitionManager 管理当前 TaskManager 所有的 ResultPartition |
int numTargetKeyGroups | 128 |
-------------------------------------- | -------------------------------------- |
Runtime state 相关 | |
AtomicBoolean isReleased | 是否释放资源 |
BufferPool bufferPool | LocalBufferPool |
boolean isFinished | 是否成功 |
SupplierWithException<BufferPool, IOException> bufferPoolFactory | ResultPartitionFactory$lambda@6982 |
Throwable cause | 异常信息 |
BufferCompressor bufferCompressor; | Buffer压缩器 |
Counter numBytesOut | 处理数据的大小bytes |
Counter numBuffersOut | 处理数据的数量 |
- 数据样例
releaseLock = {Object@6973}
consumedSubpartitions = {boolean[4]@6974} [false, false, false, false]
numUnconsumedSubpartitions = 4
subpartitions = {ResultSubpartition[4]@6975}
unicastBufferBuilders = {BufferBuilder[4]@6976}
broadcastBufferBuilder = null
idleTimeMsPerSecond = {MeterView@6977}
owningTaskName = "Flat Map (1/4)#0 (b2490d6207a4eaa9f285fb307bd31782)"
partitionIndex = 0
partitionId = {ResultPartitionID@6979} "3abe615ae9be22f64d8e5af582df91ed#0@b2490d6207a4eaa9f285fb307bd31782"
partitionType = {ResultPartitionType@6929} "PIPELINED_BOUNDED"
partitionManager = {ResultPartitionManager@6930}
numSubpartitions = 4
numTargetKeyGroups = 128
isReleased = {AtomicBoolean@6980} "false"
bufferPool = {LocalBufferPool@6981} "[size: 16, required: 5, requested: 1, available: 1, max: 16, listeners: 0,subpartitions: 4, maxBuffersPerChannel: 10, destroyed: false]"
isFinished = false
cause = null
bufferPoolFactory = {ResultPartitionFactory$lambda@6982}
bufferCompressor = null
numBytesOut = {SimpleCounter@6983}
numBuffersOut = {SimpleCounter@6984}
3.2.2. 构造方法
只是简单地 赋值操作, 没做任何变化…
public ResultPartition(
String owningTaskName,
int partitionIndex,
ResultPartitionID partitionId,
ResultPartitionType partitionType,
int numSubpartitions,
int numTargetKeyGroups,
ResultPartitionManager partitionManager,
@Nullable BufferCompressor bufferCompressor,
SupplierWithException<BufferPool, IOException> bufferPoolFactory) {
this.owningTaskName = checkNotNull(owningTaskName);
Preconditions.checkArgument(0 <= partitionIndex, "The partition index must be positive.");
this.partitionIndex = partitionIndex;
this.partitionId = checkNotNull(partitionId);
this.partitionType = checkNotNull(partitionType);
this.numSubpartitions = numSubpartitions;
this.numTargetKeyGroups = numTargetKeyGroups;
this.partitionManager = checkNotNull(partitionManager);
this.bufferCompressor = bufferCompressor;
this.bufferPoolFactory = bufferPoolFactory;
}
3.2.3. 方法相关
ResultPartition 是一个抽象类 , 实现了ResultPartitionWriter 接口… 所以要实现ResultPartitionWriter的方法.
名称 | 描述 |
void setup() throws IOException; | 初始化相关 |
ResultPartitionID getPartitionId(); | 获取ResultPartitionID |
int getNumberOfSubpartitions(); | 获取子分区的数量 |
int getNumTargetKeyGroups(); | xxx |
void emitRecord(ByteBuffer record, int targetSubpartition) throws IOException; | 将数据发送到制定的子分区 |
void broadcastRecord(ByteBuffer record) throws IOException; | 广播数据 |
void broadcastEvent(AbstractEvent event, boolean isPriorityEvent) | 广播事件 |
void setMetricGroup(TaskIOMetricGroup metrics); | 设置metric相关 |
ResultSubpartitionView createSubpartitionView( int index, BufferAvailabilityListener availabilityListener) | 构建SubpartitionView,用于读取子分区数据 |
void flushAll(); | flush所有分区的数据 |
void flush(int subpartitionIndex); | flush某一个分区的数据 |
void fail(@Nullable Throwable throwable); | 异常相关 |
void finish() throws IOException; | 执行设置分区状态为成功 |
boolean isFinished(); | 是否成功 |
void release(Throwable cause); | 执行释放资源 |
boolean isReleased(); | 资源是否释放成功 |
void close() throws Exception; | 执行分区关闭 |
- setup 初始化
/**
* Registers a buffer pool with this result partition.
*
* <p>There is one pool for each result partition, which is shared by all its sub partitions.
*
* <p>The pool is registered with the partition *after* it as been constructed in order to
* conform to the life-cycle of task registrations in the {@link TaskExecutor}.
*/
@Override
public void setup() throws IOException {
checkState(
this.bufferPool == null,
"Bug in result partition setup logic: Already registered buffer pool.");
// bufferPool = {LocalBufferPool@6981} "[
// size: 16,
// required: 5,
// requested: 1,
// available: 1,
// max: 16,
// listeners: 0,
// subpartitions: 4,
// maxBuffersPerChannel: 10,
// destroyed: false
// ]"
this.bufferPool = checkNotNull(bufferPoolFactory.get());
// 将这个分区注册到partitionManager
partitionManager.registerResultPartition(this);
}
其他的没啥特殊的就是状态的转换…
3.3. BufferWritingResultPartition
BufferWritingResultPartition 也是一个抽象类, 继承了ResultPartition .
3.3.1. 属性
一共有四个属性:
名称 | 描述 |
ResultSubpartition[] subpartitions | 子分区 |
BufferBuilder[] unicastBufferBuilders | 对于非广播模式,每个子分区都维护一个单独的BufferBuilder,该BufferBuilder可能为空 |
BufferBuilder broadcastBufferBuilder | 广播变量的BufferBuilders |
Meter idleTimeMsPerSecond = new MeterView(new SimpleCounter()) | 计数相关 |
3.3.2. 构造方法
构造方法就是赋值操作, 区别是 根据分区的数量构建了对应的 BufferBuilder[] unicastBufferBuilders .
public BufferWritingResultPartition(
String owningTaskName,
int partitionIndex,
ResultPartitionID partitionId,
ResultPartitionType partitionType,
ResultSubpartition[] subpartitions,
int numTargetKeyGroups,
ResultPartitionManager partitionManager,
@Nullable BufferCompressor bufferCompressor,
SupplierWithException<BufferPool, IOException> bufferPoolFactory) {
super(
owningTaskName,
partitionIndex,
partitionId,
partitionType,
subpartitions.length,
numTargetKeyGroups,
partitionManager,
bufferCompressor,
bufferPoolFactory);
this.subpartitions = checkNotNull(subpartitions);
// 根据子分区的数量构建
this.unicastBufferBuilders = new BufferBuilder[subpartitions.length];
}
3.3.3. 方法相关
BufferWritingResultPartition 也是一个抽象类, 继承了ResultPartition , 所以会重写 ResultPartition 的方法.
名称 | 描述 |
void setup() throws IOException; | 初始化相关 |
int getNumberOfQueuedBuffers(int targetSubpartition) | 获取所有子分区队列中buffer的数量 |
void flushSubpartition(int targetSubpartition, boolean finishProducers) | flush指定分区 |
void flushAllSubpartitions(boolean finishProducers) | flush所有分区 |
void emitRecord(ByteBuffer record, int targetSubpartition) | 将序列化结果写入buffer |
BufferBuilder appendUnicastDataForNewRecord( final ByteBuffer record, final int targetSubpartition) | 将数据写入指定分区 |
BufferBuilder requestNewUnicastBufferBuilder(int targetSubpartition) | 请求分区 |
void finishUnicastBufferBuilder(int targetSubpartition) | BufferBuilder数据写满操作. |
BufferBuilder appendUnicastDataForRecordContinuation( final ByteBuffer remainingRecordBytes, final int targetSubpartition) | 追加剩余数据 |
void broadcastRecord(ByteBuffer record) | 广播数据 |
broadcastEvent(AbstractEvent event, boolean isPriorityEvent) | 广播事件 |
ResultSubpartitionView createSubpartitionView( int subpartitionIndex, BufferAvailabilityListener availabilityListener) | 创建子分区视图 |
BufferBuilder appendBroadcastDataForNewRecord(final ByteBuffer record) | 追加广播数据 |
BufferBuilder appendBroadcastDataForRecordContinuation( final ByteBuffer remainingRecordBytes) | 最加剩余的广播数据 |
void createBroadcastBufferConsumers(BufferBuilder buffer, int partialRecordBytes) | 创建广播Buffer消费者 |
BufferBuilder requestNewBroadcastBufferBuilder() | 请求新的广播buffer |
requestNewBufferBuilderFromPool(int targetSubpartition) | 为某分区请求新的BufferBuilder |
void finishUnicastBufferBuilders() | 将所有分区的数据设置为finish |
void finishBroadcastBufferBuilder() | 将所有的广播buffer设置为finish |
3.4. BufferBuilder
不是线程安全类,用于填充{@link MemorySegment}的内容。
要访问写入的数据,请使用{@link BufferConsumer},它允许从写入的数据构建{@link Buffer}实例。
3.4.1. 属性
一共有四个属性
名称 | 描述 |
MemorySegment memorySegment; | 内存片段 |
BufferRecycler recycler; | buffer回收器 |
SettablePositionMarker positionMarker = new SettablePositionMarker(); | 指针对象,用于记录MemorySegmentx写入的位置等指针信息 |
boolean bufferConsumerCreated = false; | 是否构建Consumer |
3.4.2. 构造方法
构造方法只是赋值操作, 给MemorySegment和BufferRecycler赋值…
public BufferBuilder(MemorySegment memorySegment, BufferRecycler recycler) {
this.memorySegment = checkNotNull(memorySegment);
this.recycler = checkNotNull(recycler);
}
3.4.3. 构造BufferConsumer相关方法
此方法始终从当前writer offset 开始创建{@link BufferConsumer}。
在创建{@link BufferConsumer}之前写入{@link BufferBuilder}的数据对于该{@link BufferConsumer}将不可见。
// 从指定的offset位置, 构建BufferConsumer .
private BufferConsumer createBufferConsumer(int currentReaderPosition) {
checkState(
!bufferConsumerCreated, "Two BufferConsumer shouldn't exist for one BufferBuilder");
// 设置bufferConsumerCreated 为true
bufferConsumerCreated = true;
// 构建BufferConsumer
return new BufferConsumer(memorySegment, recycler, positionMarker, currentReaderPosition);
}
3.4.3. MemorySegment内存操作相关方法
- appendAndCommit & append
将数据写入到memorySegment .
/** Same as {@link #append(ByteBuffer)} but additionally {@link #commit()} the appending. */
public int appendAndCommit(ByteBuffer source) {
int writtenBytes = append(source);
commit();
return writtenBytes;
}
/**
* Append as many data as possible from {@code source}. Not everything might be copied if there
* is not enough space in the underlying {@link MemorySegment}
*
* @return number of copied bytes
*/
public int append(ByteBuffer source) {
checkState(!isFinished());
int needed = source.remaining();
int available = getMaxCapacity() - positionMarker.getCached();
int toCopy = Math.min(needed, available);
memorySegment.put(positionMarker.getCached(), source, toCopy);
positionMarker.move(toCopy);
return toCopy;
}
- commit
更新指针的位置
/**
* Make the change visible to the readers. This is costly operation (volatile access) thus in
* case of bulk writes it's better to commit them all together instead one by one.
*/
public void commit() {
positionMarker.commit();
}
3.4.3. 资源回收
- recycle
public void recycle() {
recycler.recycle(memorySegment);
}
3.5. BufferConsumer
BufferBuilder类中的createBufferConsumer方法, 会从MemorySegment的指定offset构建BufferConsumer .
new BufferConsumer(memorySegment, recycler, positionMarker, currentReaderPosition)
3.5.1. 属性
BufferConsumer 有三个属性一个是Buffer buffer
, 所有的memorySegment都会封装成NetworkBuffer .
剩下的CachedPositionMarker writerPosition
和 int currentReaderPosition
分别是写入的位置和当前读取位置的指针.
private final Buffer buffer;
private final CachedPositionMarker writerPosition;
private int currentReaderPosition;
3.5.2. 构造方法
BufferConsumer的构造方法中会将传入的MemorySegment
封装成NetworkBuffer
.NetworkBuffer
是一个MemorySegment
的封装, 创建的时候需要指定他们的回收器recycler。Recycler
需要实现BufferRecycler
接口, 该接口仅有一个方法recycle, 存放了MemorySegment
的回收逻辑。
BufferBuilder.PositionMarker
封装成CachedPositionMarker .
/** Constructs {@link BufferConsumer} instance with the initial reader position. */
public BufferConsumer(
MemorySegment memorySegment,
BufferRecycler recycler,
PositionMarker currentWriterPosition,
int currentReaderPosition) {
this(
new NetworkBuffer(checkNotNull(memorySegment), checkNotNull(recycler)),
currentWriterPosition,
currentReaderPosition);
}
3.5.3. 操作方法
名称 | 描述 |
Buffer build() | 构建根据信息构建新的NetworkBuffer |
skip(int bytesToSkip) | 读取指针跳到指定位置 |
BufferConsumer copy() | 复制一个新的BufferConsumer |
close() | 回收操作… |
3.6. ResultSubpartitionView
ResultSubpartitionView 是一个接口, 用于消费 {@link ResultSubpartition}实例的视图
名称 | 描述 |
BufferAndBacklog getNextBuffer() | 从队列中获取下一个{@link Buffer}的实例 |
void notifyDataAvailable(); | 通知 ResultSubpartition 的数据可供消费 |
void notifyPriorityEvent(int priorityBufferNumber) | 已经完成对 ResultSubpartition 的Event消费 |
void releaseAllResources() | 释放所有资源 |
boolean isReleased(); | 是否释放 |
void resumeConsumption() | 恢复消费数据 |
Throwable getFailureCause() | 获取失败原因 |
boolean isAvailable(int numCreditsAvailable) | 是否可用(比如信用额度) |
int unsynchronizedGetNumberOfQueuedBuffers() | 获取队列中尚未消费的BufferConsumer |
3.7. 实现类PipelinedSubpartitionView
PipelinedSubpartitionView 是ResultSubpartitionView 的实现类.
3.7.1. 属性
/**
* 标识这个视图归属于哪个 PipelinedSubpartition
* The subpartition this view belongs to.
* */
private final PipelinedSubpartition parent;
/**
* 当有数据的时候通过BufferAvailabilityListener的实现通知
* LocalInputChannel
* 或者
* CreditBasedSequenceNumberingViewReader(RemoteInputChannel)有数据到来,可以消费数据
*/
private final BufferAvailabilityListener availabilityListener;
/**
* 这个视图是否被释放
* Flag indicating whether this view has been released. */
final AtomicBoolean isReleased;
3.7.2. 构造方法
public PipelinedSubpartitionView(
PipelinedSubpartition parent, BufferAvailabilityListener listener) {
this.parent = checkNotNull(parent);
this.availabilityListener = checkNotNull(listener);
// 原子类, 资源是否释放.
this.isReleased = new AtomicBoolean();
}
3.7.3. 方法相关
PipelinedSubpartitionView 是ResultSubpartitionView接口的实现类. 所以要实现ResultSubpartitionView接口的所有方法.
名称 | 描述 |
BufferAndBacklog getNextBuffer() | 从队列中获取下一个{@link Buffer}的实例 |
void notifyDataAvailable(); | 通知 ResultSubpartition 的数据可供消费 |
void notifyPriorityEvent(int priorityBufferNumber) | 已经完成对 ResultSubpartition 的Event消费 |
void releaseAllResources() | 释放所有资源 |
boolean isReleased(); | 是否释放 |
void resumeConsumption() | 恢复消费数据 |
Throwable getFailureCause() | 获取失败原因 |
boolean isAvailable(int numCreditsAvailable) | 是否可用(比如信用额度) |
int unsynchronizedGetNumberOfQueuedBuffers() | 获取队列中尚未消费的BufferConsumer |
其实大部分的方法都是直接调用该 该PipelinedSubpartitionView 所属 PipelinedSubpartition的方法.
- notifyDataAvailable
这个方法重点说一下吧, 当有数据到来的时候,会通知inputchannel有数据到了,进而inputchannel的实现就可以进行消费了.
availabilityListener的实现就两个 一个是LocalInputChannel 另一个是 CreditBasedSequenceNumberingViewReader(针对RemoteInputChannel)有数据到来,可以消费数据 .
@Override
public void notifyDataAvailable() {
//回调接口,通知inputchannel有数据到来
availabilityListener.notifyDataAvailable();
}
3.8. PipelinedResultPartition
PipelinedResultPartition是抽象类BufferWritingResultPartition 的实现类
一个任务的结果输出,通过pipelined (streamed) 传输到接收器。
这个结果分区实现同时用于批处理和流处理。
对于流式传输,它支持低延迟传输(确保数据在100毫秒内发送)或无限制传输,而对于批处理,它仅在缓冲区已满时传输。
此外,对于流式使用,这通常会限制缓冲区积压的长度,以避免有太多的数据在传输中,而对于批处理,我们不限制这一点。
3.8.1. 属性
就三个属性, 一个锁, 以及一个数组用于标识子分区是否可以用于消费和另一个暂停消费的子分区的数量 .
/**
* The lock that guard release operations (which can be asynchronously propagated from the
* networks threads.
*/
private final Object releaseLock = new Object();
/**
* A flag for each subpartition indicating whether it was already consumed or not, to make
* releases idempotent.
*/
@GuardedBy("releaseLock")
private final boolean[] consumedSubpartitions;
/**
* The total number of references to subpartitions of this result. The result partition can be
* safely released, iff the reference count is zero.
*/
@GuardedBy("releaseLock")
private int numUnconsumedSubpartitions;
3.8.2 构造方法
public PipelinedResultPartition(
String owningTaskName,
int partitionIndex,
ResultPartitionID partitionId,
ResultPartitionType partitionType,
ResultSubpartition[] subpartitions,
int numTargetKeyGroups,
ResultPartitionManager partitionManager,
@Nullable BufferCompressor bufferCompressor,
SupplierWithException<BufferPool, IOException> bufferPoolFactory) {
super(
owningTaskName,
partitionIndex,
partitionId,
checkResultPartitionType(partitionType),
subpartitions,
numTargetKeyGroups,
partitionManager,
bufferCompressor,
bufferPoolFactory);
this.consumedSubpartitions = new boolean[subpartitions.length];
this.numUnconsumedSubpartitions = subpartitions.length;
}
3.8.3 方法相关
- onConsumedSubpartition
/**
* 一旦释放了所有子分区读取器,pipelined分区就会自动释放。
*
* 这是因为pipelined分区不能被多次使用或重新连接。
*
* The pipelined partition releases automatically once all subpartition readers are released.
* That is because pipelined partitions cannot be consumed multiple times, or reconnect.
*/
@Override
void onConsumedSubpartition(int subpartitionIndex) {
// 如果资源已被释放, 则直接return
if (isReleased()) {
return;
}
final int remainingUnconsumed;
// we synchronize only the bookkeeping section, to avoid holding the lock during any
// calls into other components
//加锁
synchronized (releaseLock) {
// 如果该子分区已经处于可消费的状态,直接返回
if (consumedSubpartitions[subpartitionIndex]) {
// repeated call - ignore
return;
}
// 设置该子分区处于可消费的状态
consumedSubpartitions[subpartitionIndex] = true;
// 减少 remainingUnconsumed 值
remainingUnconsumed = (--numUnconsumedSubpartitions);
}
LOG.debug(
"{}: Received consumed notification for subpartition {}.", this, subpartitionIndex);
if (remainingUnconsumed == 0) {
// 如果没有可以消费的 子分区. 通知partitionManager改子分区可以释放资源.
partitionManager.onConsumedPartition(this);
} else if (remainingUnconsumed < 0) {
throw new IllegalStateException(
"Received consume notification even though all subpartitions are already consumed.");
}
}
3.9.ResultSubpartition
ResultPartition 由 ResultSubpartition 构成, ResultSubpartition 是一个抽象类.
ResultSubpartition 的数量由下游消费 Task 数和 DistributionPattern 来决定。
例如,如果是 FORWARD,则下游只有一个消费者;如果是 SHUFFLE,则下游消费者的数量和下游算子的并行度一样
3.9.1. 属性
/**
* 子分区的信息
* The info of the subpartition to identify it globally within a task.
* */
protected final ResultSubpartitionInfo subpartitionInfo;
/**
* 该子分区属于那个Resultpartition
* The parent partition this subpartition belongs to. */
protected final ResultPartition parent;
3.9.2. ResultSubpartitionInfo
用于标识ResultSubpartition的信息. 一共就两个属性
private final int partitionIdx;
private final int subPartitionIdx;
数据实例 :
3.9.3. BufferAndBacklog
{@link Buffer} 和backlog长度的组合,指示子分区中有多少非事件缓冲区可用。
就4个属性
// 缓存数据
private final Buffer buffer;
// 挤压的buffer数量
private final int buffersInBacklog;
// Buffer的数据类型
private final Buffer.DataType nextDataType;
// 序号
private final int sequenceNumber;
3.9.4. 方法相关
- public abstract boolean add(BufferConsumer bufferConsumer, int partialRecordLength)
添加给定的buffer。
请求可以同步执行,也可以异步执行,具体取决于实现。
在添加新的{@link BufferConsumer}之前,先前添加的必须处于finished状态。
由于性能原因,这仅在数据读取期间强制执行。
可以在前一个缓冲区使用者仍然打开时添加优先级事件,在这种情况下,打开的缓冲区使用者将被超越。
/**
* 添加给定的buffer。
* 请求可以同步执行,也可以异步执行,具体取决于实现。
*
* 在添加新的{@link BufferConsumer}之前,先前添加的必须处于finished状态。
* 由于性能原因,这仅在数据读取期间强制执行。
* 可以在前一个缓冲区使用者仍然打开时添加优先级事件,在这种情况下,打开的缓冲区使用者将被超越。
*
* Adds the given buffer.
*
* <p>The request may be executed synchronously, or asynchronously, depending on the
* implementation.
*
* <p><strong>IMPORTANT:</strong> Before adding new {@link BufferConsumer} previously added must
* be in finished state.
*
* Because of the performance reasons, this is only enforced during the data reading.
*
* Priority events can be added while the previous buffer consumer is still open,
* in which case the open buffer consumer is overtaken.
*
* @param bufferConsumer the buffer to add (transferring ownership to this writer)
* @param partialRecordLength the length of bytes to skip in order to start with a complete
* record, from position index 0 of the underlying {@cite MemorySegment}.
* @return true if operation succeeded and bufferConsumer was enqueued for consumption.
* @throws IOException thrown in case of errors while adding the buffer
*/
public abstract boolean add(BufferConsumer bufferConsumer, int partialRecordLength) throws IOException;
四. StreamRecord
从source获取的数据会进行封装, 变为 StreamRecord .
一共有三个属性T value
为真正的数据,long timestamp
为事件戳.boolean hasTimestamp
标识是否有时间戳.
/** The actual value held by this record. */
private T value;
/** The timestamp of the record. */
private long timestamp;
/** Flag whether the timestamp is actually set. */
private boolean hasTimestamp;
五. Output 相关
5.1. Output
Output是一个接口,用于发送数据.
a {@link org.apache.flink.streaming.api.aperators.StreamOperator}将提供此接口的对象,
该对象可用于从operator
发出元素和其他消息,例如barriers
和watermarks
。
一共有三个方法 .
名称 | 描述 |
void emitWatermark(Watermark mark); | 发送Watermark |
void collect(OutputTag outputTag, StreamRecord record); | 发送数据 |
void emitLatencyMarker(LatencyMarker latencyMarker); | 发送延迟标记 |
5.2. CountingOutput
CountingOutput实现了Output 接口. 主要是对Output的数据进行进一步的封装.
有两个属性 Output<StreamRecord<OUT>> output
是真实的output实现( 比如CopyingChainingOutput
)Counter numRecordsOut
只是一个计数器, 实现类为SimpleCounter
, 仅仅是对数据的条数做了一个累加.
private final Output<StreamRecord<OUT>> output;
private final Counter numRecordsOut;
- 方法
方法大部分都是调用的Output<StreamRecord<OUT>> output
来实现的.
@Override
public void emitWatermark(Watermark mark) {
output.emitWatermark(mark);
}
@Override
public void emitLatencyMarker(LatencyMarker latencyMarker) {
output.emitLatencyMarker(latencyMarker);
}
@Override
public void collect(StreamRecord<OUT> record) {
numRecordsOut.inc();
output.collect(record);
}
@Override
public <X> void collect(OutputTag<X> outputTag, StreamRecord<X> record) {
numRecordsOut.inc();
output.collect(outputTag, record);
}
@Override
public void close() {
output.close();
}
5.2. CopyingChainingOutput
CopyingChainingOutput
是CountingOutput
实现类中Output<StreamRecord<OUT>> output
的实现类之一.
5.3 ChainingOutput
ChainingOutput 是CopyingChainingOutput 的父类, 主体的实现都在这里,所以重点看一下.
5.3.1 属性
// 算子类
// input = {StreamFlatMap@7086}
protected final Input<T> input;
// 计数器, 用于计算处理的多少条数据
protected final Counter numRecordsIn;
// Watermark 相关
protected final WatermarkGauge watermarkGauge = new WatermarkGauge();
// 状态管理 OperatorChain
protected final StreamStatusProvider streamStatusProvider;
// null
@Nullable protected final OutputTag<T> outputTag;
// ChainingOutput$lambda@7089
@Nullable protected final AutoCloseable closeable;
5.3.2. 方法
- 数据输出相关
@Override
public void collect(StreamRecord<T> record) {
if (this.outputTag != null) {
// we are not responsible for emitting to the main output.
return;
}
pushToOperator(record);
}
@Override
public <X> void collect(OutputTag<X> outputTag, StreamRecord<X> record) {
if (OutputTag.isResponsibleFor(this.outputTag, outputTag)) {
pushToOperator(record);
}
}
- collect 方法最终是通过 pushToOperator(record) 处理数据.
以flatMap为例, 会直接调用flatMap的实现类StreamFlatMap
的processElement
开始处理数据… 这个函数会调用用户定义好的userFunction里面的flatMap函数处理数据
pushToOperator
protected <X> void pushToOperator(StreamRecord<X> record) {
try {
// we know that the given outputTag matches our OutputTag so the record
// must be of the type that our operator expects.
// 我们知道给定的outputTag与我们的outputTag匹配,所以记录必须是我们的操作符期望的类型。
@SuppressWarnings("unchecked")
StreamRecord<T> castRecord = (StreamRecord<T>) record;
// 处理数据的 数量 +1
numRecordsIn.inc();
// 设置KeyContextElement
// 比如调用 StreamFlatMap 算子的 setKeyContextElement
input.setKeyContextElement(castRecord);
// 设置处理数据
// 比如调用 StreamFlatMap 算子的 processElement
input.processElement(castRecord);
}
catch (Exception e) {
throw new ExceptionInChainedOperatorException(e);
}
}
StreamFlatMap
的processElement
函数
@Override
public void processElement(StreamRecord<IN> element) throws Exception {
// 处理时间戳
collector.setTimestamp(element);
// 调用用户定义好的函数,SocketWindowWordCount处理数据
userFunction.flatMap(element.getValue(), collector);
}
5.3.3. emitWatermark
发送Watermark
@Override
public void emitWatermark(Watermark mark) {
try {
watermarkGauge.setCurrentWatermark(mark.getTimestamp());
if (streamStatusProvider.getStreamStatus().isActive()) {
input.processWatermark(mark);
}
}
catch (Exception e) {
throw new ExceptionInChainedOperatorException(e);
}
}
5.3.4. emitLatencyMarker
处理延迟标记
@Override
public void emitLatencyMarker(LatencyMarker latencyMarker) {
try {
input.processLatencyMarker(latencyMarker);
}
catch (Exception e) {
throw new ExceptionInChainedOperatorException(e);
}
}
六. RecordWriter
RecordWriter负责将Task处理的数据输出, 然后下游Task就可以继续处理了。
RecordWriter面向的是StreamRecord, 直接处理算子的输出结果。
ResultPatitionWriter面向的是Buffer, 起到承上启下的作用。
RecordWriter比ResultPartitionWriter的层级要高, 底层依赖于ResultPartitionWriter。
RecordWriter类负责将StreamRecord进行序列化, 调用DataOutputSerializer, 再调用BufferBuilder写入MemorySegment中
(每个Task都有自己的LocalBufferPool, LocalBufferPool中包含了多个MemorySegment)
数据发送端最重要的角色是RecordWriter和ResultPartition。
ecordWriter负责将数据流中的元素序列化,存入到ResultSubpartition中。
RecordWriter具有两个子类:BroadcastRecordWriter和ChannelSelectorRecordWriter,
分别负责广播形式写入数据和根据channel选择器向指定的channel写入数据。
这两个子类的emit方法主要逻辑都位于父类RecordWriter的emit方法。
6.1. 属性
//底层的 ResultPartition
protected final ResultPartitionWriter targetPartition;
//channel的数量,即 sub-partition的数量
protected final int numberOfChannels;
//序列化
protected final DataOutputSerializer serializer;
// 基于George Marsaglia 提出的 XORShift 算法实现了一个随机数发生器。
// 这个RNG比基准测试中的{@link java.util.Random}快4.5倍,代价是放弃线程安全性。
// 因此建议为每个线程创建一个新的 {@link XORShiftRandom}
protected final Random rng = new XORShiftRandom();
// 是否每一条数据都flush , 默认是false
// 采用OutputFlusher定时器,每100毫秒flush一次.
protected final boolean flushAlways;
/**
* //定时强制 flush 输出buffer , 默认值 100 毫秒
*
* 假如产出的数据记录较少无法完整地填充一个 MemorySegment,
* 那么 ResultSubpartition 可能会一直处于不可被消费的状态。
* 而为了保证产出的记录能够及时被消费,就需要及时进行 flush,从而确保下游能更及时地处理数据。
* 在 RecordWriter 中有一个 OutputFlusher 会定时触发 flush,
* 间隔可以通过 [算子] DataStream.setBufferTimeout() 来控制。
*
*
* The thread that periodically flushes the output, to give an upper latency bound. */
@Nullable private final OutputFlusher outputFlusher;
/**
* flusher 异常相关
*
* To avoid synchronization overhead on the critical path, best-effort error tracking is enough here.
*/
private Throwable flusherException;
private volatile Throwable volatileFlusherException;
private int volatileFlusherExceptionCheckSkipCount;
// 默认 OutputFlusher 默认flush的时间: 100 毫秒
private static final int VOLATILE_FLUSHER_EXCEPTION_MAX_CHECK_SKIP_COUNT = 100;
6.2. 构造方法
RecordWriter(ResultPartitionWriter writer, long timeout, String taskName) {
this.targetPartition = writer;
this.numberOfChannels = writer.getNumberOfSubpartitions();
//序列化器,用于指的一提将一条记录序列化到多个buffer中
this.serializer = new DataOutputSerializer(128);
checkArgument(timeout >= -1);
this.flushAlways = (timeout == 0);
if (timeout == -1 || timeout == 0) {
outputFlusher = null;
} else {
//根据超时时间创建一个定时 flush 输出 buffer 的线程
String threadName =
taskName == null
? DEFAULT_OUTPUT_FLUSH_THREAD_NAME
: DEFAULT_OUTPUT_FLUSH_THREAD_NAME + " for " + taskName;
outputFlusher = new OutputFlusher(threadName, timeout);
outputFlusher.start();
}
}
6.3. emit 相关方法
名称 | 描述 |
void emit(T record, int targetSubpartition) | 发送数据到指定的分区 |
abstract void emit(T record) | 发送数据 |
void randomEmit(T record) | 随机选择分区发送数据 |
abstract void broadcastEmit(T record) | 以广播的方式发送数据 |
- emit(T record, int targetSubpartition)
protected void emit(T record, int targetSubpartition) throws IOException {
checkErroneous();
// 序列化 record,存入到目标channel的缓存中
// targetPartition = {PipelinedResultPartition@7312} "PipelinedResultPartition f3e53e3fbc3eab68b25ea79f80873233#0@9bd406565dca544a85576fd06acc0fc0 [PIPELINED_BOUNDED, 4 subpartitions, 4 pending consumptions]"
// releaseLock = {Object@7858}
// consumedSubpartitions = {boolean[4]@7859} [false, false, false, false]
// numUnconsumedSubpartitions = 4
// subpartitions = {ResultSubpartition[4]@7860}
// unicastBufferBuilders = {BufferBuilder[4]@7861}
// broadcastBufferBuilder = null
// idleTimeMsPerSecond = {MeterView@7862}
// owningTaskName = "Source: Socket Stream (1/1)#0 (9bd406565dca544a85576fd06acc0fc0)"
// partitionIndex = 0
// partitionId = {ResultPartitionID@7864} "f3e53e3fbc3eab68b25ea79f80873233#0@9bd406565dca544a85576fd06acc0fc0"
// partitionType = {ResultPartitionType@6650} "PIPELINED_BOUNDED"
// partitionManager = {ResultPartitionManager@6651}
// numSubpartitions = 4
// numTargetKeyGroups = 128
// isReleased = {AtomicBoolean@7865} "false"
// bufferPool = {LocalBufferPool@7866} "[size: 16, required: 5, requested: 5, available: 1, max: 16, listeners: 0,subpartitions: 4, maxBuffersPerChannel: 10, destroyed: false]"
// isFinished = false
// cause = null
// bufferPoolFactory = {ResultPartitionFactory$lambda@7867}
// bufferCompressor = null
// numBytesOut = {SimpleCounter@7868}
// numBuffersOut = {SimpleCounter@7869}
// PipelinedResultPartition 的父类 BufferWritingResultPartition # emitRecord
targetPartition.emitRecord(serializeRecord(serializer, record), targetSubpartition);
// flushAlways : false
if (flushAlways) {
// flush 操作...
targetPartition.flush(targetSubpartition);
}
}
- emit(T record)
抽象类,用于发送数据.
/** This is used to send regular records. */
public abstract void emit(T record) throws IOException;
- randomEmit(T record)
随机发送数据…
/** This is used to send LatencyMarks to a random target channel. */
public void randomEmit(T record) throws IOException {
checkErroneous();
int targetSubpartition = rng.nextInt(numberOfChannels);
emit(record, targetSubpartition);
}
6.4. broadcastEvent 相关
- 广播事件
public void broadcastEvent(AbstractEvent event) throws IOException {
broadcastEvent(event, false);
}
public void broadcastEvent(AbstractEvent event, boolean isPriorityEvent) throws IOException {
targetPartition.broadcastEvent(event, isPriorityEvent);
if (flushAlways) {
flushAll();
}
}
- 广播数据
/** This is used to broadcast streaming Watermarks in-band with records. */
public abstract void broadcastEmit(T record) throws IOException;
6.5. ChannelSelectorRecordWriter
ChannelSelectorRecordWriter 是RecordWriter抽象类的实现类.
通过channelSelector对象判断数据需要发往下游的哪个channel。 keyBy算子用的正是这个RecordWriter。
6.5.1. 属性
这个类只有一个属性ChannelSelector . 决定一条记录应该写入哪一个channel, 即 sub-partition
//决定一条记录应该写入哪一个channel, 即 sub-partition
private final ChannelSelector<T> channelSelector;
KeyGroupStreamPartitioner
6.5.2. ChannelSelector
{@link ChannelSelector} 决定数据记录如何写入到 logical channels .
名称 | 描述 |
void setup(int numberOfChannels); | 设置 初始化 channel selector 的数量 |
int selectChannel(T record); | 返回数据写入的 logical channel 的索引 为broadcast channel selectors 调用此方法是非法的, 在这种情况下,此方法可能无法实现(例如,通过抛出{@link UnsupportedOperationException})。 |
boolean isBroadcast(); | 返回channel selector是否始终选择所有输出通道。 |
这个ChannelSelector 的种类有点多[九种???],等有空再细看 …
6.5.3. 构造函数
// writer = {PipelinedResultPartition@6639} "PipelinedResultPartition db1576884668472b75f882792173d0fa#0@eb44184ef213a1ddc71dc739d2f1edee [PIPELINED_BOUNDED, 4 subpartitions, 4 pending consumptions]"
// releaseLock = {Object@6643}
// consumedSubpartitions = {boolean[4]@6644} [false, false, false, false]
// numUnconsumedSubpartitions = 4
// subpartitions = {ResultSubpartition[4]@6645}
// unicastBufferBuilders = {BufferBuilder[4]@6646}
// broadcastBufferBuilder = null
// idleTimeMsPerSecond = {MeterView@6647}
// owningTaskName = "Flat Map (1/4)#0 (eb44184ef213a1ddc71dc739d2f1edee)"
// partitionIndex = 0
// partitionId = {ResultPartitionID@6649} "db1576884668472b75f882792173d0fa#0@eb44184ef213a1ddc71dc739d2f1edee"
// partitionType = {ResultPartitionType@6650} "PIPELINED_BOUNDED"
// partitionManager = {ResultPartitionManager@6651}
// numSubpartitions = 4
// numTargetKeyGroups = 128
// isReleased = {AtomicBoolean@6652} "false"
// bufferPool = {LocalBufferPool@6653} "[size: 16, required: 5, requested: 1, available: 1, max: 16, listeners: 0,subpartitions: 4, maxBuffersPerChannel: 10, destroyed: false]"
// isFinished = false
// cause = null
// bufferPoolFactory = {ResultPartitionFactory$lambda@6654}
// bufferCompressor = null
// numBytesOut = {SimpleCounter@6655}
// numBuffersOut = {SimpleCounter@6656}
// channelSelector = {KeyGroupStreamPartitioner@6627} "HASH"
// timeout = 100
// taskName = "Flat Map"
// this.channelSelector = {KeyGroupStreamPartitioner@6627} "HASH"
// numberOfChannels = 4
ChannelSelectorRecordWriter(
ResultPartitionWriter writer,
ChannelSelector<T> channelSelector,
long timeout,
String taskName) {
super(writer, timeout, taskName);
this.channelSelector = checkNotNull(channelSelector);
this.channelSelector.setup(numberOfChannels);
}
6.5.4. emit 方法
使用指定的channelSelector 将数据发送到指定的子分区…
@Override
public void emit(T record) throws IOException {
// record = {SerializationDelegate@7309}
// instance = {StreamRecord@7658} "Record @ (undef) : 正正正"
// serializer = {StreamElementSerializer@7319}
// channelSelector = {RebalancePartitioner@7311} "REBALANCE"
// nextChannelToSendTo = 1
// numberOfChannels = 4
//channelSelector确定目标子分区
emit(record, channelSelector.selectChannel(record));
}
6.5.5. broadcastEmit(T record)
将数据发送到所有的channel …
@Override
public void broadcastEmit(T record) throws IOException {
checkErroneous();
// Emitting to all channels in a for loop can be better than calling
// ResultPartitionWriter#broadcastRecord because the broadcastRecord
// method incurs extra overhead.
ByteBuffer serializedRecord = serializeRecord(serializer, record);
for (int channelIndex = 0; channelIndex < numberOfChannels; channelIndex++) {
serializedRecord.rewind();
emit(record, channelIndex);
}
if (flushAlways) {
flushAll();
}
}