文章目录
demo
AsyncDataStream.unorderedWait(stream, new RichAsyncFunction<JSONObject, JSONObject>() {
public transient ThreadPoolExecutor executor;
@Override
public void open(Configuration parameters) throws Exception {
executor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.MINUTES, new LinkedBlockingDeque<>());
}
@Override
public void asyncInvoke(JSONObject input, ResultFuture<JSONObject> resultFuture) throws Exception {
CompletableFuture.supplyAsync(new Supplier<JSONObject>() {
@Override
public JSONObject get() {
return input;
}
}).thenAccept((JSONObject value)->{
resultFuture.complete(Collections.singleton(input));
});
}
}, 1000, TimeUnit.MILLISECONDS, 100);
构建AsyncWaitOperator
不管选择哪种OutputMode,都得为AsyncWaitOperator生成对应的AsyncWaitOperatorFactory和Transformation。
/**
* 为DataStream添加一个OneInputTransformation,它包含了AsyncWaitOperator
*/
private static <IN, OUT> SingleOutputStreamOperator<OUT> addOperator(
DataStream<IN> in,
AsyncFunction<IN, OUT> func, // 自定义异步处理函数
long timeout,
int bufSize,
OutputMode mode) {
// 当前DataStream的输出类型
TypeInformation<OUT> outTypeInfo = TypeExtractor.getUnaryOperatorReturnType(
func,
AsyncFunction.class,
0,
1,
new int[]{1, 0},
in.getType(),
Utils.getCallLocationName(),
true);
// 创建AsyncWaitOperatorFactory,将其交给Transformation持有。
AsyncWaitOperatorFactory<IN, OUT> operatorFactory = new AsyncWaitOperatorFactory<>(
in.getExecutionEnvironment().clean(func),
timeout,
bufSize,
mode);
// 生成异步StreamOperator对应的Transformation
return in.transform("async wait operator", outTypeInfo, operatorFactory);
}
在初始化OperatorChain时会利用AsyncWaitOperatorFactory来创建AsyncWaitOperator,这其中有2个核心操作。
/**
* 构建StreamGraph时,会利用AsyncWaitOperatorFactory来创建AsyncWaitOperator
*/
@Override
public StreamOperator createStreamOperator(StreamTask containingTask, StreamConfig config, Output output) {
// 核心 1:构建AsyncWaitOperator
AsyncWaitOperator asyncWaitOperator = new AsyncWaitOperator(
asyncFunction, // 用户自定义的异步处理函数
timeout,
capacity,
outputMode,
mailboxExecutor);
// 核心 2:初始化AsyncWaitOperator,根据OutputMode来创建不同的StreamElementQueue
asyncWaitOperator.setup(containingTask, config, output);
// 返回创建好的AsyncWaitOperator
return asyncWaitOperator;
}
核心 1:构造AsyncWaitOperator,这会让AsyncWaitOperator“合理又合法的”持有AsyncFunction。
/**
* AsyncWaitOperator的构造方法
*/
public AsyncWaitOperator(
@Nonnull AsyncFunction<IN, OUT> asyncFunction,
long timeout,
int capacity,
@Nonnull AsyncDataStream.OutputMode outputMode, // 它决定了异步处理任务队列的类型,从而决定了用户数据异步处理后是否严格按照输入顺序去输出
@Nonnull MailboxExecutor mailboxExecutor) {
// AsyncWaitOperator会持有AsyncFunction的引用
super(asyncFunction);
// 链化策略
setChainingStrategy(ChainingStrategy.HEAD);
Preconditions.checkArgument(capacity > 0, "The number of concurrent async operation should be greater than 0.");
// 队列长度
this.capacity = capacity;
// 是否保证输入、输出元素的顺序严格一致
this.outputMode = Preconditions.checkNotNull(outputMode, "outputMode");
// 异步处理逻辑的超时时间
this.timeout = timeout;
// 执行作业的线程池
this.mailboxExecutor = mailboxExecutor;
}
核心 2:对AsyncWaitOperator进行初始化设置,最核心的就是根据AsyncDataStream.OutputMode,确定保存数据元素所需的StreamElementQueue队列,并包装好用于发送下游的Output组件。
/**
* AsyncWaitOperator创建出来后,会执行该方法进行初始化操作。根据OutputMode来创建不同的StreamElementQueue
*/
@Override
public void setup(StreamTask<?, ?> containingTask, StreamConfig config, Output<StreamRecord<OUT>> output) {
// 调用父类初始化逻辑
super.setup(containingTask, config, output);
// 创建StreamElement的序列化器
this.inStreamElementSerializer = new StreamElementSerializer<>(
getOperatorConfig().<IN>getTypeSerializerIn1(getUserCodeClassloader()));
/**
* 根据AsyncDataStream.OutputMode确定使用哪种类型的StreamElementQueue,
* 所有需要异步处理的(DataStream中的)StreamElement,都要在StreamElementQueue队列中排队。
*/
switch (outputMode) {
case ORDERED:
// 需要保证输出的数据有序
queue = new OrderedStreamElementQueue<>(capacity);
break;
case UNORDERED:
// 无需保证输出的数据有序
queue = new UnorderedStreamElementQueue<>(capacity);
break;
default:
throw new IllegalStateException("Unknown async mode: " + outputMode + '.');
}
// 包装Output组件
this.timestampedCollector = new TimestampedCollector<>(output);
}
作为OneInputStreamOperator,会调用AsyncWaitOperator#processElement()方法来处理DataStream中的每一个StreamElement。
/**
* 核心:处理StreamElement
*/
@Override
public void processElement(StreamRecord<IN> element) throws Exception {
/**
* 核心:将StreamElement(StreamRecord or Watermark)添加到StreamElementQueue队列中,返回ResultFuture
*/
final ResultFuture<OUT> entry = addToWorkQueue(element);
// ResultHandler是ResultFuture的实现子类,可以在AsyncFunction中进行“异步处理完成”和“异步处理异常”
final ResultHandler resultHandler = new ResultHandler(element, entry);
// 如果配置了超时时间
if (timeout > 0L) {
// 计算超时时刻
final long timeoutTimestamp = timeout + getProcessingTimeService().getCurrentProcessingTime();
// 注册Timer,在执行异步处理逻辑时一旦发生超时,就调用AsyncFunction#timeout()方法
final ScheduledFuture<?> timeoutTimer = getProcessingTimeService().registerTimer(
timeoutTimestamp,
timestamp -> userFunction.timeout(element.getValue(), resultHandler));
// Timer交给ResultHandler
resultHandler.setTimeoutTimer(timeoutTimer);
}
/**
* 核心:调用自定义的异步处理函数AsyncFunction的asyncInvoke()方法,处理完毕后就会将结果交给ResultFuture(手动调用它的complete方法)
*/
userFunction.asyncInvoke(element.getValue(), resultHandler);
}
核心 1:将StreamElement添加到StreamElementQueue队列
DataStream中需要被执行异步处理逻辑的StreamElement,首先要被add到StreamElementQueue队列中进行排队。StreamElement的具体类型不同,被添加的StreamElementQueue队列的具体实现也不同。
/**
* 将DataStream中的StreamElement(StreamRecord或Watermark)添加到StreamElementQueue队列中
*/
private ResultFuture<OUT> addToWorkQueue(StreamElement streamElement) throws InterruptedException {
Optional<ResultFuture<OUT>> queueEntry;
// 将StreamElement(StreamRecord或Watermark)添加到StreamElementQueue队列。
// 如果添加失败,说明队列已满,需要当前线程将执行机会让给MailboxExecutor来执行用户自定义处理逻辑
while (!(queueEntry = queue.tryPut(streamElement)).isPresent()) {
mailboxExecutor.yield();
}
// “添加队列”成功,返回ResultFuture
return queueEntry.get();
}
根据AsyncDataStream.OutputMode会确定好我们要使用的StreamElementQueue队列具体是哪个。
OrderedStreamElementQueue类型
如果是OrderedStreamElementQueue,它采用Queue< StreamElementQueueEntry >队列来保存输入进来的StreamElement。
在将StreamElement添加到队列之前,会先根据StreamElement的类型(StreamRecord or Watermark),构造出不同的StreamElementQueueEntry(StreamRecordQueueEntry or WatermarkQueueEntry)。这些StreamElementQueueEntry最终会被add到OrderedStreamElementQueue队列中。
/**
* 根据StreamElement的类型(StreamRecord or Watermark),构造不同的StreamElementQueueEntry(ResultFuture的子接口)
*/
private StreamElementQueueEntry<OUT> createEntry(StreamElement streamElement) {
if (streamElement.isRecord()) {
return new StreamRecordQueueEntry<>((StreamRecord<?>) streamElement);
}
if (streamElement.isWatermark()) {
return new WatermarkQueueEntry<>((Watermark) streamElement);
}
throw new UnsupportedOperationException("Cannot enqueue " + streamElement);
}
准备好StreamElement对应的StreamElementQueueEntry后,就该将其添加到OrderedStreamElementQueue中了。
/**
* 尝试将StreamElement放到队列中,如果添加成功就返回ResultFuture,否则返回Optional.EMPTY
*/
@Override
public Optional<ResultFuture<OUT>> tryPut(StreamElement streamElement) {
// 只有当队列有剩余空间的情况下才能将StreamElement加入队列
if (queue.size() < capacity) {
/**
* 根据StreamElement的类型(StreamRecord or Watermark),
* 构造不同的StreamElementQueueEntry(StreamRecordQueueEntry or WatermarkQueueEntry)
*/
StreamElementQueueEntry<OUT> queueEntry = createEntry(streamElement);
// 将StreamElementQueueEntry添加到StreamElementQueue队列中
queue.add(queueEntry);
LOG.debug("Put element into ordered stream element queue. New filling degree " +
"({}/{}).", queue.size(), capacity);
return Optional.of(queueEntry);
} else {
LOG.debug("Failed to put element into ordered stream element queue because it " +
"was full ({}/{}).", queue.size(), capacity);
// 如果超出队列容量,返回EMPTY
return Optional.empty();
}
}
只要队列未满,就会add成功,返回ResultFuture。需要额外注意的是,队列中存储的StreamElementQueueEntry,本质就是ResultFuture
UnorderedStreamElementQueue类型
如果是UnorderedStreamElementQueue类型,它采用Deque< Segment >双端队列来存储Segment ,其中Segment由2部分组成:
// 未完成的Set集合
private final Set<StreamElementQueueEntry<OUT>> incompleteElements;
// 已完成的Queue队列
private final Queue<StreamElementQueueEntry<OUT>> completedElements;
StreamElement最终要被转换成StreamElementQueueEntry,保存到Segment中。针对StreamElement的类型(StreamRecord or Watermark),会对应生成不同的StreamElementQueueEntry,并采用各自不同的add(到Segment的)方式。需要注意的是,Segment中可能会有1个或多个数据元素。
如果StreamElement是StreamRecord类型,生成StreamElementQueueEntry并add到Segment的逻辑如下:
/**
* StreamRecord作为StreamElementQueueEntry,添加到Segment中。Segment会根据这个StreamElementQueueEntry是否异步处理完毕,
* 从而将其添加到Segment中的未完成Set集合 or 已完成Queue队列中
*/
private StreamElementQueueEntry<OUT> addRecord(StreamRecord<?> record) {
// ensure that there is at least one segment
Segment<OUT> lastSegment;
if (segments.isEmpty()) {
// 如果队列是空的,那就创建一个Segment并放到队列的Last
lastSegment = addSegment(capacity);
} else {
// 队列不空,将Last Segment取出来
lastSegment = segments.getLast();
}
// 将StreamRecord包装成StreamElementQueueEntry
StreamElementQueueEntry<OUT> queueEntry = new SegmentedStreamRecordQueueEntry<>(record, lastSegment);
/**
* 将这个StreamElement对应的StreamElementQueueEntry添加到Segment中,并视的异步处理完成情况,将其添加到已完成 or 未完成队列中
*/
lastSegment.add(queueEntry);
return queueEntry;
}

如果双端队列Deque< Segment >是空的,那就创建一个新的Segment并添加到双端队列中;如果双端队列不空,那就将Last Segment取出来;
private Segment<OUT> addSegment(int capacity) {
Segment newSegment = new Segment(capacity);
segments.addLast(newSegment);
return newSegment;
}
最后将StreamRecord对应的StreamElementQueueEntry添加到这个Segment中。
如果StreamElement是Watermark类型,生成StreamElementQueueEntry并add到Segment的逻辑如下:
/**
* StreamElementQueueEntry用于容纳Watermark,但1个Segment内只会有1个Watermark对应的StreamElementQueueEntry
*/
private StreamElementQueueEntry<OUT> addWatermark(Watermark watermark) {
Segment<OUT> watermarkSegment;
/**
* Watermark对应的StreamElementQueueEntry在Deque<Segment>中“自成一派”,独自占用1个Segment,
* 相当于Watermark“切分”了StreamElement。
*/
if (!segments.isEmpty() && segments.getLast().isEmpty()) {
// 如果Deque<Segment>双端队列不空 && 双端队列的Last Segment是空的,那就用这个Last Segment来容纳Watermark
watermarkSegment = segments.getLast();
} else {
// 双端队列为null || 双端队列的Last Segment不为null,那就创建一个新的Segment用来容纳Watermark
watermarkSegment = addSegment(1);
}
// 将Watermark包装成StreamElementQueueEntry
StreamElementQueueEntry<OUT> watermarkEntry = new WatermarkQueueEntry<>(watermark);
// 根据当前StreamElementQueueEntry的完成情况,将其添加到Segment内的未完成Set集合 or 已完成队列中
watermarkSegment.add(watermarkEntry);
// 核心:为Deque<Segment>的Last位置添加一个新的Segment,这也证明了Segment对待Watermark的态度是“允许自成一派”
addSegment(capacity);
return watermarkEntry;
}
Segment对待Watermark的态度,简而言之就是Watermark对应的StreamElementQueueEntry在双端队列Deque< Segment >中“自成一派”,相当于用Watermark将StreamElement“切分开来”。
最后将Watermark包装成对应的StreamElementQueueEntry后,add到Deque< Segment >双端队列中。最后将这个双端队列的Last位置添加一个新的Segment,这样做是为了更好的放下一个StreamElement。
/**
* 为Deque<Segment>双端队列的Last位置add一个新的Segment
*/
private Segment<OUT> addSegment(int capacity) {
Segment newSegment = new Segment(capacity);
segments.addLast(newSegment);
return newSegment;
}

在对应将StreamRecord、Watermark包装成对应的StreamElementQueueEntry,并添加到Segment后,表示本轮“StreamElement入队”操作就执行完毕了。
/**
* 将StreamElement中的StreamRecord、Watermark添加到Deque<Segment>双端队列中
*/
@Override
public Optional<ResultFuture<OUT>> tryPut(StreamElement streamElement) {
// 检查是否超出队列长度
if (size() < capacity) {
StreamElementQueueEntry<OUT> queueEntry;
/**
* 根据StreamElement的类型(StreamRecord or Watermark),构造不同的StreamElementQueueEntry,执行不同的添加逻辑。
* 对于StreamRecord而言,一个Segment中可能放有N个数据元素;对于Watermark而言,一个Segment中只会放1个数据元素;
*/
if (streamElement.isRecord()) {
// 如果队列中没有Segment就创建一个新Segment出来,将StreamRecord对应的StreamElementQueueEntry添加到这个Segment中
queueEntry = addRecord((StreamRecord<?>) streamElement);
} else if (streamElement.isWatermark()) {
// 如果队列中的最后一个Segment为空,就创建一个新Segment来容纳Watermark。即每当遇到一个Watermark,都会使用新的Segment
queueEntry = addWatermark((Watermark) streamElement);
} else {
throw new UnsupportedOperationException("Cannot enqueue " + streamElement);
}
// StreamElementQueueEntry的个数累加
numberOfEntries++;
LOG.debug("Put element into unordered stream element queue. New filling degree " +
"({}/{}).", size(), capacity);
return Optional.of(queueEntry);
} else {
LOG.debug("Failed to put element into unordered stream element queue because it " +
"was full ({}/{}).", size(), capacity);
return Optional.empty();
}
}
每当add一个StreamRecord or Watermark,都会累加统计个数。每当有一个StreamElement顺利执行完异步处理逻辑(即调用Complete方法),就会-1。
如果这个统计个数 < 默认的Segment容量100,那就能继续向Deque中继续添加StreamElement。
一旦超过默认容量,主线程就会Block住这个addSegment操作,转而去全力处理Mailbox中的Mail,即加快处理“积压”的外部异步查询。
核心 2:执行用户自定义处理函数AsyncFunction#asyncInvoke()方法
当StreamElement被添加到指定的StreamElementQueue队列后,接着就该调用用户自定义处理函数AsyncFunction#asyncInvoke()方法来对队列中的StreamElement执行异步处理逻辑。
回顾demo,在自定义处理函数的最后,手动调用了ResultFuture.complete()方法。作为ResultFuture接口的实现子类,ResultHandler提供了对“执行完异步处理逻辑的结果”进行后续处理的具体实现逻辑:
/**
* 在AsyncFunction#asyncInvoke()方法中执行用户的自定义异步处理逻辑,
* 处理完成后需要手动调用ResultFuture.complete()方法(由实现子类ResultHandler提供具体的实现逻辑:
* 将用来标记异步计算是否完成的标志位置为true、clear掉超时Timer、将元素输出到下游)
*/
@Override
public void complete(Collection<OUT> results) {
Preconditions.checkNotNull(results, "Results must not be null, use empty collection to emit nothing");
if (!completed.compareAndSet(false, true)) {
return;
}
// 在MailboxExecutor线程池中执行ResultFuture#complete()方法
processInMailbox(results);
}
/**
* 在MailboxExecutor线程池中执行ResultFuture#complete()方法,通知持有数据元素的StreamElementQueue队列,该元素已处理完毕。
* 接着clear掉超时时间的Timer,最后将已处理完毕的元素输出给下游
*/
private void processInMailbox(Collection<OUT> results) {
// move further processing into the mailbox thread
mailboxExecutor.execute(
// 对执行完异步处理逻辑的结果,进行下一步处理
() -> processResults(results),
"Result in AsyncWaitOperator of input %s", results);
}
/**
* 对执行完异步处理逻辑的结果进行下一步处理:移除超时Timer、将处理结果发送给下游
*/
private void processResults(Collection<OUT> results) {
// 一旦将StreamElementQueue队列中的某个entry处理完毕后,就clear掉超时Timer,因为已经不需要它了
if (timeoutTimer != null) {
timeoutTimer.cancel(true);
}
// 将执行完异步处理逻辑的结果,更新给StreamElementQueue队列里的StreamElementQueueEntry
resultFuture.complete(results);
// now output all elements from the queue that have been completed (in the correct order)
// 将已处理完毕的元素输出给下游
outputCompletedElement();
}
对执行完异步处理逻辑的结果,会clear掉超时Timer、将其更新给队列里的StreamElementQueueEntry(ResultFuture就是StreamElementQueueEntry)、通过Output组件发送给下游。
1.更新StreamElementQueueEntry
处理OrderedStreamElementQueue中的StreamElementQueueEntry
对于StreamRecord的后续处理:将已经执行完异步处理逻辑的结果,保存到StreamRecordQueueEntry的成员变量中。
// 已经执行完异步处理逻辑的结果
private Collection<OUT> completedElements;
/**
* 对应StreamRecord的处理逻辑
*/
@Override
public void complete(Collection<OUT> result) {
// 将执行完异步处理逻辑的结果,保存给成员变量
this.completedElements = Preconditions.checkNotNull(result);
}
处理UnorderedStreamElementQueue中的Segment内的StreamRecordQueueEntry
对于StreamRecord的后续处理:将处理权下放给Segment
/**
* 在执行完异步处理逻辑后,需要手动调用ResultFuture#complete()方法,以下即是具体实现逻辑
*/
@Override
public void complete(Collection<OUT> result) {
super.complete(result);
// 调用Segment#completed()方法
segment.completed(this);
}
Segment会将这个StreamElementQueueEntry从未完成的Set集合remove掉,并add到已处理完成的Queue队列中。
/**
* Signals that an entry finished computation.
* 当StreamElementQueueEntry中的数据被异步处理完毕后,需要调用ResultFuture#complete()方法,
* 方法内会调用Segment#completed()方法,将这个StreamElementQueueEntry从未完成的Set集合,remove到已处理完成的Queue队列中
*/
void completed(StreamElementQueueEntry<OUT> elementQueueEntry) {
if (incompleteElements.remove(elementQueueEntry)) {
completedElements.add(elementQueueEntry);
}
}
2.发送下游
将已经执行完异步处理逻辑的结果在StreamElementQueueEntry更新后,就会通知AsyncWaitOperator将最终结果发送给下游。
private void outputCompletedElement() {
// 如果队列头部的StreamElementQueueEntry已经完成了异步处理
if (queue.hasCompletedElements()) {
// 为了避免阻塞MailboxExecutor线程,这里仅仅会发送出1个StreamElementQueueEntry。
queue.emitCompletedElement(timestampedCollector);
// 如果有更多的StreamElementQueueEntry(内的StreamElement)已经被异步处理完毕,随后会在MailboxExecutor中将它们发送出去
if (queue.hasCompletedElements()) {
mailboxExecutor.execute(this::outputCompletedElement, "AsyncWaitOperator#outputCompletedElement");
}
}
}
具体的判断逻辑、发送,由具体的StreamElementQueue提供实现逻辑。
对于OrderedStreamElementQueue队列
首先会检查OrderedStreamElementQueue队列头部的StreamElementQueueEntry是否已经完成了异步处理:
/**
* 检查队列的头部的StreamElementQueueEntry是否已经被异步处理完毕了
*/
@Override
public boolean hasCompletedElements() {
return !queue.isEmpty() && queue.peek().isDone();
}
如果OrderedStreamElementQueue队列头部的StreamElementQueueEntry的异步处理逻辑已经执行完毕,那就将其交给Output组件
/**
* 从StreamElementQueue队列的头部取出一个StreamElementQueueEntry,通过Output组件发送给下游算子
*/
@Override
public void emitCompletedElement(TimestampedCollector<OUT> output) {
if (hasCompletedElements()) {
// 取出队列头部的StreamElementQueueEntry,将其交给Output组件用来发送下游
final StreamElementQueueEntry<OUT> head = queue.poll();
// 将队列的Head StreamElementQueueEntry交给Output组件
head.emitResult(output);
}
}
StreamRecordQueueEntry的发送逻辑:
/**
* 将所有已被执行完异步处理逻辑的结果,经由Output组件发送下游
*/
@Override
public void emitResult(TimestampedCollector<OUT> output) {
output.setTimestamp(inputRecord);
// 遍历所有已经执行完异步处理逻辑的结果,将其交由Output组件发送给下游
for (OUT r : completedElements) {
output.collect(r);
}
}
对于UnorderedStreamElementQueue队列
首先检查双端队列Deque< Segment >的头部是否有已经完成的Segment,即双端队列中的First Segment中的“已完成Queue队列”不为null。
/**
* 判断双端队里Deque<Segment>中是否有已经执行完异步处理逻辑的元素
*/
@Override
public boolean hasCompletedElements() {
// 双端队列Deque<Segment>不为空 && 双端队列的First Segment中有已经执行完异步处理逻辑的元素(也就是Segment中的Queue<StreamElementQueueEntry>不为null)
return !this.segments.isEmpty() && this.segments.getFirst().hasCompleted();
}
接下来会将双端队列中的First Segment取出
/**
* 将已经异步处理完成的数据元素发送给下游
*/
@Override
public void emitCompletedElement(TimestampedCollector<OUT> output) {
if (segments.isEmpty()) {
return;
}
/**
* 核心:获取Deque<Segment<OUT>>队列中的第一个Segment
*/
final Segment currentSegment = segments.getFirst();
// 从这个First Segment的“已处理完成”的Queue中poll出一个已经异步处理完毕的元素
numberOfEntries -= currentSegment.emitCompleted(output);
/**
* 如果当前Segment中没有保存的元素,并且队列中至少还有一个Segment,那就将这个Segment从队列中弹出,让它被GC。
* 通过这种设计,可以通过Watermark将一串数据“切分”,放到不同的Segment中。同时,只有当Deque<Segment<OUT>>队列中的First Segment中
* 的数据全部弹出后,才会去读下一个Segment,这保证了乱序程度。
*/
if (segments.size() > 1 && currentSegment.isEmpty()) {
segments.pop();
}
}
实际的“collect操作”是由Segment经手执行的,也就是将这个Segment中的“已完成队列”中的StreamElementQueueEntry全都poll出来
/**
* 在将“经过异步执行逻辑处理过的StreamElement”发送给下游时,会从“已完成队列”中poll出Head StreamElementQueueEntry,
* 并将其交给Output组件来发送给下游。从Segment的设计可以看出,Segment放弃了“元素顺序保证”,谁执行完异步处理逻辑,谁就会被放到“已完成队列”中,
* 不会因为有人长时间未completed而阻塞!
*/
int emitCompleted(TimestampedCollector<OUT> output) {
// 从Segment内“已完成队列”中poll出一个(已被异步逻辑执行完毕的)Head StreamElementQueueEntry
final StreamElementQueueEntry<OUT> completedEntry = completedElements.poll();
if (completedEntry == null) {
return 0;
}
// 将这个StreamElementQueueEntry内的StreamRecord统统交给Output组件
completedEntry.emitResult(output);
return 1;
}
然后将这个Head StreamElementQueueEntry内的StreamRecord统统交给Output组件
/**
* 将所有已被执行完异步处理逻辑的结果,经由Output组件发送下游
*/
@Override
public void emitResult(TimestampedCollector<OUT> output) {
output.setTimestamp(inputRecord);
for (OUT r : completedElements) {
output.collect(r);
}
}
1590

被折叠的 条评论
为什么被折叠?



