深入理解Flink异步I/O原理

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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值