1、简介

Flink的特点是高吞吐低延迟。但是Flink中的某环节的数据处理逻辑需要和外部系统交互,调用耗时不可控会显著降低集群性能。这时候就可能需要使用异步算子让耗时操作不需要等待结果返回就可以继续下面的耗时操作。

2、本章可以了解到啥

  • 异步算子源码分析
  • 异步算子为啥能够保证有序性
  • flinksql中怎么自定义使用异步lookup join

3、异步算子的测试代码

import java.io.Serializable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; /**
* 网上copy的模拟一个耗时的异步操作
*/
public class AsyncIODemo implements Serializable { private final ExecutorService executorService = Executors.newFixedThreadPool(4); public CompletableFuture<String> pullData(final String source) { CompletableFuture<String> completableFuture = new CompletableFuture<>(); executorService.submit(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
/**
* 前面睡眠几秒后,调用一下完成方法,拼接一个结果字符串
*/
completableFuture.complete("Output value: " + source);
}); return completableFuture;
}
}
import org.apache.flink.streaming.api.datastream.AsyncDataStream;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.async.AsyncFunction;
import org.apache.flink.streaming.api.functions.async.ResultFuture;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit; /**
* 网上copy的代码
*/
public class AsyncTest { public static void main(String[] args) throws Exception {
/**
* 获取Flink执行环境并设置并行度为1,方便后面观测
*/
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1); /**
* 构造一个DataStreamSource的序列
*/
DataStreamSource stream = env.fromElements("11", "22", "33", "44"); /**
* 使用AsyncDataStream构造一个异步顺序流,这里异步顺序流从名字就可以看出来虽然是异步的,但是却可以保持顺序,
* 这个后面源码分析可以知道原因
*/
SingleOutputStreamOperator asyncStream = AsyncDataStream.orderedWait(stream, new AsyncFunction<String, String>() {
@Override
public void asyncInvoke(String input, ResultFuture<String> resultFuture) throws Exception {
/**
* 这里调用模拟的获取异步请求结果,并返回一个CompletableFuture
*/
CompletableFuture<String> future = new AsyncIODemo().pullData(input);
/**
* 注册一个future处理完成的回调,当future处理完成拿到结果后,调用resultFuture的
* complete方法真正吐出数据
*/
future.whenCompleteAsync((d,t) ->{
resultFuture.complete(Arrays.asList(d));
});
}
// 设置最长异步调用超时时间为10秒
}, 10, TimeUnit.SECONDS);
asyncStream.print();
env.execute();
}
}

4、异步算子源码分析

4.1、AsyncDataStream

package org.apache.flink.streaming.api.datastream;

import org.apache.flink.annotation.PublicEvolving;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.api.java.Utils;
import org.apache.flink.api.java.typeutils.TypeExtractor;
import org.apache.flink.streaming.api.functions.async.AsyncFunction;
import org.apache.flink.streaming.api.operators.async.AsyncWaitOperator;
import org.apache.flink.streaming.api.operators.async.AsyncWaitOperatorFactory; import java.util.concurrent.TimeUnit; /**
* 用于将AsyncFunction应用到数据流的一个helper类
*
* <pre>{@code
* DataStream<String> input = ...
* AsyncFunction<String, Tuple<String, String>> asyncFunc = ...
*
* AsyncDataStream.orderedWait(input, asyncFunc, timeout, TimeUnit.MILLISECONDS, 100);
* }</pre>
*/
@PublicEvolving
public class AsyncDataStream { /** 异步操作的输出模式,有序或者无序. */
public enum OutputMode {
ORDERED,
UNORDERED
} private static final int DEFAULT_QUEUE_CAPACITY = 100; /**
* flag_2,添加一个AsyncWaitOperator.
*
* @param in The {@link DataStream} where the {@link AsyncWaitOperator} will be added.
* @param func {@link AsyncFunction} wrapped inside {@link AsyncWaitOperator}.
* @param timeout for the asynchronous operation to complete
* @param bufSize The max number of inputs the {@link AsyncWaitOperator} can hold inside.
* @param mode Processing mode for {@link AsyncWaitOperator}.
* @param <IN> Input type.
* @param <OUT> Output type.
* @return A new {@link SingleOutputStreamOperator}
*/
private static <IN, OUT> SingleOutputStreamOperator<OUT> addOperator(
DataStream<IN> in,
AsyncFunction<IN, OUT> func,
long timeout,
int bufSize,
OutputMode mode) { TypeInformation<OUT> outTypeInfo =
TypeExtractor.getUnaryOperatorReturnType(
func,
AsyncFunction.class,
0,
1,
new int[] {1, 0},
in.getType(),
Utils.getCallLocationName(),
true); /**
这里生成了一个AsyncWaitOperatorFactory
*/
AsyncWaitOperatorFactory<IN, OUT> operatorFactory =
new AsyncWaitOperatorFactory<>(
in.getExecutionEnvironment().clean(func), timeout, bufSize, mode); return in.transform("async wait operator", outTypeInfo, operatorFactory);
} /**
* 添加一个AsyncWaitOperator。输出流无顺序。
*
* @param in Input {@link DataStream}
* @param func {@link AsyncFunction}
* @param timeout for the asynchronous operation to complete
* @param timeUnit of the given timeout
* @param capacity The max number of async i/o operation that can be triggered
* @param <IN> Type of input record
* @param <OUT> Type of output record
* @return A new {@link SingleOutputStreamOperator}.
*/
public static <IN, OUT> SingleOutputStreamOperator<OUT> unorderedWait(
DataStream<IN> in,
AsyncFunction<IN, OUT> func,
long timeout,
TimeUnit timeUnit,
int capacity) {
return addOperator(in, func, timeUnit.toMillis(timeout), capacity, OutputMode.UNORDERED);
} /**
* 添加一个AsyncWaitOperator。输出流无顺序。
* @param in Input {@link DataStream}
* @param func {@link AsyncFunction}
* @param timeout for the asynchronous operation to complete
* @param timeUnit of the given timeout
* @param <IN> Type of input record
* @param <OUT> Type of output record
* @return A new {@link SingleOutputStreamOperator}.
*/
public static <IN, OUT> SingleOutputStreamOperator<OUT> unorderedWait(
DataStream<IN> in, AsyncFunction<IN, OUT> func, long timeout, TimeUnit timeUnit) {
return addOperator(
in, func, timeUnit.toMillis(timeout), DEFAULT_QUEUE_CAPACITY, OutputMode.UNORDERED);
} /**
* flag_1,添加一个AsyncWaitOperator。处理输入记录的顺序保证与输入记录的顺序相同
*
* @param in Input {@link DataStream}
* @param func {@link AsyncFunction}
* @param timeout for the asynchronous operation to complete
* @param timeUnit of the given timeout
* @param capacity The max number of async i/o operation that can be triggered
* @param <IN> Type of input record
* @param <OUT> Type of output record
* @return A new {@link SingleOutputStreamOperator}.
*/
public static <IN, OUT> SingleOutputStreamOperator<OUT> orderedWait(
DataStream<IN> in,
AsyncFunction<IN, OUT> func,
long timeout,
TimeUnit timeUnit,
int capacity) {
return addOperator(in, func, timeUnit.toMillis(timeout), capacity, OutputMode.ORDERED);
} /**
* 添加一个AsyncWaitOperator。处理输入记录的顺序保证与输入记录的顺序相同
* @param in Input {@link DataStream}
* @param func {@link AsyncFunction}
* @param timeout for the asynchronous operation to complete
* @param timeUnit of the given timeout
* @param <IN> Type of input record
* @param <OUT> Type of output record
* @return A new {@link SingleOutputStreamOperator}.
*/
public static <IN, OUT> SingleOutputStreamOperator<OUT> orderedWait(
DataStream<IN> in, AsyncFunction<IN, OUT> func, long timeout, TimeUnit timeUnit) { return addOperator(
in, func, timeUnit.toMillis(timeout), DEFAULT_QUEUE_CAPACITY, OutputMode.ORDERED);
}
}

如上从测试代码开始调用链为:AsyncDataStream.orderedWait -> addOperator,然后addOperator中new了一个AsyncWaitOperatorFactory。然后到这里其实可以告一段落了,因为没有必要往下看了,这个时候就需要猜了,一般我们类名叫XXFactory基本都是工厂模式,然后工厂生产的就是XX了,这里就是生成AsyncWaitOperator对象的工厂了,然后我们就可以直接在AsyncWaitOperator类的构造方法第一行打个断点,看看啥时候会进去了。为啥要这样做,因为我们看到的Flink源码其实并不是一个线性的执行过程,架构图如下

他的代码实际上并不是都在一个节点执行,虽然我们在本地调试,但是也是在模拟的一个本地集群中执行,怎么模拟出不同的节点呢,很明显要通过线程,也就是说不同的节点用不同的线程来代表并执行。所以我们无脑断点是没法看到全貌的。看代码的一个技巧,根据各方面的经验猜测,比如这里就是根据类名的特点进行猜测。

4.2、AsyncWaitOperator

我们在AsyncWaitOperator类的所有公共方法和构造方法里打个断点,debug运行程序进入调试

很明显这个构造方法,在一个独立的sink线程中运行,如果还按照上面的方式断点,估计找一辈子都找不到了

public AsyncWaitOperator(
@Nonnull AsyncFunction<IN, OUT> asyncFunction,
long timeout,
int capacity,
@Nonnull AsyncDataStream.OutputMode outputMode,
@Nonnull ProcessingTimeService processingTimeService,
@Nonnull MailboxExecutor mailboxExecutor) {
super(asyncFunction); setChainingStrategy(ChainingStrategy.ALWAYS); 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.processingTimeService = Preconditions.checkNotNull(processingTimeService); this.mailboxExecutor = mailboxExecutor;
}

我们看一下构造方法的内容,发现都是一些初始化操作,看着没啥营养,看代码的另外一个技巧:抓大放小,路边的野花不要理睬,忽略一些不重要的初始化和参数校验等代码,重点关注大的流程的东西。

我们继续直接放开往下运行,直到下一个断点

@Override
public void setup(
StreamTask<?, ?> containingTask,
StreamConfig config,
Output<StreamRecord<OUT>> output) {
super.setup(containingTask, config, output); this.inStreamElementSerializer =
new StreamElementSerializer<>(
getOperatorConfig().<IN>getTypeSerializerIn1(getUserCodeClassloader())); switch (outputMode) {
case ORDERED:
queue = new OrderedStreamElementQueue<>(capacity);
break;
case UNORDERED:
queue = new UnorderedStreamElementQueue<>(capacity);
break;
default:
throw new IllegalStateException("Unknown async mode: " + outputMode + '.');
} this.timestampedCollector = new TimestampedCollector<>(super.output);
}

一眼望去就发现下面switch case那里比较有用,根据名字可以知道,这里根据outputMode判断分别实例化有序的队列和无需的队列,联想到AsyncDataStream类里的几个orderedWait和unorderedWait方法,很快就能想到是否有序这个队列就是关键了。好了没什么可以留恋了,继续执行到下一个断点吧!

初始化状态,没啥可留恋的,先跳过继续到下一个断点

@Override
public void open() throws Exception {
super.open(); this.isObjectReuseEnabled = getExecutionConfig().isObjectReuseEnabled(); if (recoveredStreamElements != null) {
for (StreamElement element : recoveredStreamElements.get()) {
if (element.isRecord()) {
processElement(element.<IN>asRecord());
} else if (element.isWatermark()) {
processWatermark(element.asWatermark());
} else if (element.isLatencyMarker()) {
processLatencyMarker(element.asLatencyMarker());
} else {
throw new IllegalStateException(
"Unknown record type "
+ element.getClass()
+ " encountered while opening the operator.");
}
}
recoveredStreamElements = null;
}
}

如上从7行开始貌似是开始处理数据了,但是根据recoveredStreamElements这个名称一看,很显然recovered是恢复的意思,这里判断是否为空,不为空再做,很明显是做修复数据相关的逻辑,我们处理数据的正主都没找到这里很明显没啥意义,属于路边的野花,直接忽略到下一个断点去。

@Override
public void processElement(StreamRecord<IN> record) throws Exception {
StreamRecord<IN> element;
// copy the element avoid the element is reused
if (isObjectReuseEnabled) {
//noinspection unchecked
element = (StreamRecord<IN>) inStreamElementSerializer.copy(record);
} else {
element = record;
} // add element first to the queue
final ResultFuture<OUT> entry = addToWorkQueue(element); final ResultHandler resultHandler = new ResultHandler(element, entry); // register a timeout for the entry if timeout is configured
if (timeout > 0L) {
resultHandler.registerTimeout(getProcessingTimeService(), timeout);
} userFunction.asyncInvoke(element.getValue(), resultHandler);
}

很明显根据方法名称可以知道这里就是在处理真正的数据了,反复断点几次,可以发现,每一条数据都会进来这个方法一次

这个方法的参数就是source流里的一个元素,下面我们再看一下addToWorkQueue方法吧

/**
将给定的流元素添加到操作符的流元素队列中。该操作会阻塞,直到元素被添加。
*/
private ResultFuture<OUT> addToWorkQueue(StreamElement streamElement)
throws InterruptedException { Optional<ResultFuture<OUT>> queueEntry;
while (!(queueEntry = queue.tryPut(streamElement)).isPresent()) {
mailboxExecutor.yield();
} return queueEntry.get();
}

这个方法就是将前面source里的元素放入前面new出来的队列,本例这里是一个有序的队列OrderedStreamElementQueue,并返回了一个ResultFuture对象,我们需要看一下这个对象是个啥

4.3、ResultFuture

@PublicEvolving
public interface ResultFuture<OUT> {
/**
* 将所有结果放在Collection中,然后输出。
*/
void complete(Collection<OUT> result); /**
* 将异常输出
*/
void completeExceptionally(Throwable error);
}

我们再来看下tryPut是如何包装出了一个ResultFuture对象的

4.4、OrderedStreamElementQueue

@Internal
public final class OrderedStreamElementQueue<OUT> implements StreamElementQueue<OUT> { private static final Logger LOG = LoggerFactory.getLogger(OrderedStreamElementQueue.class); /** Capacity of this queue. */
private final int capacity; /** Queue for the inserted StreamElementQueueEntries. */
private final Queue<StreamElementQueueEntry<OUT>> queue; public OrderedStreamElementQueue(int capacity) {
Preconditions.checkArgument(capacity > 0, "The capacity must be larger than 0."); this.capacity = capacity;
this.queue = new ArrayDeque<>(capacity);
} @Override
public boolean hasCompletedElements() {
return !queue.isEmpty() && queue.peek().isDone();
} @Override
public void emitCompletedElement(TimestampedCollector<OUT> output) {
if (hasCompletedElements()) {
final StreamElementQueueEntry<OUT> head = queue.poll();
head.emitResult(output);
}
} @Override
public List<StreamElement> values() {
List<StreamElement> list = new ArrayList<>(this.queue.size());
for (StreamElementQueueEntry e : queue) {
list.add(e.getInputElement());
}
return list;
} @Override
public boolean isEmpty() {
return queue.isEmpty();
} @Override
public int size() {
return queue.size();
} @Override
public Optional<ResultFuture<OUT>> tryPut(StreamElement streamElement) {
if (queue.size() < capacity) {
StreamElementQueueEntry<OUT> queueEntry = createEntry(streamElement); 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); return Optional.empty();
}
} 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);
}
}

我们重点关注一下52行以下的部分,可以看到new了一个StreamElementQueueEntry对象放入了queue队列中,那就需要看一下StreamRecordQueueEntry类了

4.5、StreamRecordQueueEntry

@Internal
class StreamRecordQueueEntry<OUT> implements StreamElementQueueEntry<OUT> {
@Nonnull private final StreamRecord<?> inputRecord; private Collection<OUT> completedElements; StreamRecordQueueEntry(StreamRecord<?> inputRecord) {
this.inputRecord = Preconditions.checkNotNull(inputRecord);
} @Override
public boolean isDone() {
return completedElements != null;
} @Nonnull
@Override
public StreamRecord<?> getInputElement() {
return inputRecord;
} @Override
public void emitResult(TimestampedCollector<OUT> output) {
output.setTimestamp(inputRecord);
for (OUT r : completedElements) {
output.collect(r);
}
} @Override
public void complete(Collection<OUT> result) {
this.completedElements = Preconditions.checkNotNull(result);
}
}

如上之后,现在已经可以有一个大概的认识了,就是随着程序的运行,一个个的数据被封装成了StreamRecordQueueEntry对象,并阻塞的放入了OrderedStreamElementQueue队列中了,这个队列中的StreamRecordQueueEntry对象有一些方法标志性的方法,如:isDone,根据名字就可以知道是否完成的意思;emitResult方法如果写过flink程序的人一看到output.collect(r)这种代码就知道是向下游发出数据的方法;complete方法字母意思就是一个完成动作方法,内容就是把传入的数据判空后赋给了成员变量completedElements。

我们继续回到processElement方法的主线上来,

// 首先将元素添加到队列中
final ResultFuture<OUT> entry = addToWorkQueue(element);
final ResultHandler resultHandler = new ResultHandler(element, entry); // 如果配置了timeout,则为条目注册一个超时,这里的timeout也就是测试代码里的10s
if (timeout > 0L) {
resultHandler.registerTimeout(getProcessingTimeService(), timeout);
} userFunction.asyncInvoke(element.getValue(), resultHandler);

关注上面的最后一行,执行了asyncInvoke方法,也就回到了测试代码里覆写的asyncInvoke方法里了

/**
* 使用AsyncDataStream构造一个异步顺序流,这里异步顺序流从名字就可以看出来虽然是异步的,但是却可以保持顺序,
* 这个后面源码分析可以知道原因
*/
SingleOutputStreamOperator asyncStream = AsyncDataStream.orderedWait(stream, new AsyncFunction<String, String>() {
@Override
public void asyncInvoke(String input, ResultFuture<String> resultFuture) throws Exception {
/**
* 这里调用模拟的获取异步请求结果,并返回一个CompletableFuture
*/
CompletableFuture<String> future = new AsyncIODemo().pullData(input);
/**
* 注册一个future处理完成的回调,当future处理完成拿到结果后,调用resultFuture的
* complete方法真正吐出数据
*/
future.whenCompleteAsync((d,t) ->{
resultFuture.complete(Arrays.asList(d));
});
}
// 设置最长异步调用超时时间为10秒
}, 10, TimeUnit.SECONDS);

这时候我们可以打个断点到如上测试代码的17行上,然后执行进入方法,可以看到实际上回到了org.apache.flink.streaming.api.operators.async.AsyncWaitOperator.ResultHandler这个内部类里的complete方法

private void outputCompletedElement() {
/**
判断这个OrderedStreamElementQueue队列有没有完成了的元素,参见上面代码
@Override
public boolean hasCompletedElements() {
return !queue.isEmpty() && queue.peek().isDone();
}
其实就是查看了一下队列头的元素StreamRecordQueueEntry,调用了一下isDone方法
@Override
public boolean isDone() {
return completedElements != null;
}
就是判断成员变量是不是空,因为上一步已经赋值了,所以这里isDone就返回true了 */
if (queue.hasCompletedElements()) {
/**
调用了一下OrderedStreamElementQueue队列的emitCompletedElement方法, @Override
public void emitCompletedElement(TimestampedCollector<OUT> output) {
if (hasCompletedElements()) {
final StreamElementQueueEntry<OUT> head = queue.poll();
head.emitResult(output);
}
}
移除队列的头元素StreamElementQueueEntry,并调用其emitResult方法
@Override
public void emitResult(TimestampedCollector<OUT> output) {
output.setTimestamp(inputRecord);
for (OUT r : completedElements) {
output.collect(r);
}
}
这里就是真正的循环调用collect把数据吐出到下游去了
*/
queue.emitCompletedElement(timestampedCollector);
// if there are more completed elements, emit them with subsequent mails
if (queue.hasCompletedElements()) {
try {
mailboxExecutor.execute(
this::outputCompletedElement,
"AsyncWaitOperator#outputCompletedElement");
} catch (RejectedExecutionException mailboxClosedException) {
// This exception can only happen if the operator is cancelled which means all
// pending records can be safely ignored since they will be processed one more
// time after recovery.
LOG.debug(
"Attempt to complete element is ignored since the mailbox rejected the execution.",
mailboxClosedException);
}
}
}
} /** A handler for the results of a specific input record. */
private class ResultHandler implements ResultFuture<OUT> {
/** Optional timeout timer used to signal the timeout to the AsyncFunction. */
private ScheduledFuture<?> timeoutTimer;
/** Record for which this result handler exists. Used only to report errors. */
private final StreamRecord<IN> inputRecord;
/**
* The handle received from the queue to update the entry. Should only be used to inject the
* result; exceptions are handled here.
*/
private final ResultFuture<OUT> resultFuture;
/**
* A guard against ill-written AsyncFunction. Additional (parallel) invokations of {@link
* #complete(Collection)} or {@link #completeExceptionally(Throwable)} will be ignored. This
* guard also helps for cases where proper results and timeouts happen at the same time.
*/
private final AtomicBoolean completed = new AtomicBoolean(false); ResultHandler(StreamRecord<IN> inputRecord, ResultFuture<OUT> resultFuture) {
this.inputRecord = inputRecord;
this.resultFuture = resultFuture;
} @Override
public void complete(Collection<OUT> results) {
Preconditions.checkNotNull(
results, "Results must not be null, use empty collection to emit nothing"); // cas修改一下completed的状态,不成功就返回
if (!completed.compareAndSet(false, true)) {
return;
} processInMailbox(results);
} private void processInMailbox(Collection<OUT> results) {
// move further processing into the mailbox thread
mailboxExecutor.execute(
() -> processResults(results),
"Result in AsyncWaitOperator of input %s",
results);
} private void processResults(Collection<OUT> results) {
/**
如果超时的Timer对象不为空,则将定时任务取消掉,因为这里已经是在完成方法里调用了,
数据都完成处理了,这个数据的超时任务就可以取消了
*/
if (timeoutTimer != null) {
// canceling in mailbox thread avoids
// https://issues.apache.org/jira/browse/FLINK-13635
timeoutTimer.cancel(true);
} /**
这里调用了一下StreamRecordQueueEntry的complete方法将成员变量completedElements
赋值了,可以参见上面StreamRecordQueueEntry类
*/
resultFuture.complete(results);
// 这里看上面第1行代码
outputCompletedElement();
} }

我们可以从上面的ResultHandler类的complete方法开始看,具体可以参见上面注释,总结起来就是如下几步

  1. 取消当前ResultHandler对象的超时定时任务
  2. 调用StreamRecordQueueEntry的complete方法将成员变量completedElements赋值
  3. 判断OrderedStreamElementQueue队列的队头元素StreamRecordQueueEntry的completedElements成员变量是不是不为空
  4. 第3步不为空,则调用OrderedStreamElementQueue队列的emitCompletedElement方法移除队列的头元素StreamElementQueueEntry并调用emitResult方法真正向下游吐出数据

从上面可以看出每次随着completableFuture的complete方法的调用,都会判断队头的元素是否处理完,处理完就移除队头元素并向吐出数据。所以异步算子每次来数据经过processElement方法,就已经将数据元素封装成StreamElementQueueEntry对象并放到了队列中,虽然异步算子执行过程是异步,每个元素的完成时间没有顺序,但是由于每个元素完成后,都是判断的队头元素有没有完成,完成后也是移除队头并向下游吐数据。所以整体过程还是按照processElement处理顺序也就是上游给过来的数据顺序严格有序的。

5、flinksql自定义AsyncLookupFunction

通常flinksql使用外部的数据源都需要引入一个flinksql-connector-xx这种jar包,比如我们想以kafka为流表join一个redis的维表,那么这时候查询redis的维表,通常使用的就是lookup join。但是网上提供的例子基本都是同步的lookup join,在有些场景下为了提高吞吐就需要使用异步的lookup join。详细实现可以直接看代码:https://gitee.com/rongdi/flinksql-connector-redis

Flink的异步算子的原理及使用的更多相关文章

  1. flink DataStream API使用及原理

    传统的大数据处理方式一般是批处理式的,也就是说,今天所收集的数据,我们明天再把今天收集到的数据算出来,以供大家使用,但是在很多情况下,数据的时效性对于业务的成败是非常关键的. Spark 和 Flin ...

  2. Flink中异步AsyncIO的实现 (源码分析)

    先上张图整体了解Flink中的异步io 阿里贡献给flink的,优点就不说了嘛,官网上都有,就是写库不会柱塞性能更好 然后来看一下, Flink 中异步io主要分为两种 一种是有序Ordered 一种 ...

  3. Atitit.异步编程技术原理与实践attilax总结

    Atitit.异步编程技术原理与实践attilax总结 1. 俩种实现模式 类库方式,以及语言方式,java futuretask ,c# await1 2. 事件(中断)机制1 3. Await 模 ...

  4. Spring源码学习之:@async 方法上添加该注解实现异步调用的原理

    在我们使用spring框架的过程中,在很多时候我们会使用@async注解来异步执行某一些方法,提高系统的执行效率.今天我们来探讨下 spring 是如何完成这个功能的.    spring 在扫描be ...

  5. Vue(基础七)_webpack(webpack异步加载原理)

    ---恢复内容开始--- 一.前言 1.webpack异步加载原理’                                           2.webpack.ensure原理     ...

  6. Atitit. Async await 优缺点 异步编程的原理and实现 java c# php

    Atitit. Async await 优缺点 异步编程的原理and实现 java c# php 1. async & await的来源1 2. 异步编程history1 2.1. 线程池 2 ...

  7. @async 方法上添加该注解实现异步调用的原理

    在我们使用spring框架的过程中,在很多时候我们会使用@async注解来异步执行某一些方法,提高系统的执行效率.今天我们来探讨下 spring 是如何完成这个功能的. spring 在扫描bean的 ...

  8. Spring @async 方法上添加该注解实现异步调用的原理

    Spring @async 方法上添加该注解实现异步调用的原理 学习了:https://www.cnblogs.com/shangxiaofei/p/6211367.html 使用异步方法进行方法调用 ...

  9. asap异步执行实现原理

    目录 为什么分析asap asap概述 asap源码解析-Node版 参考 1.为什么分析asap 在之前的文章 async和await是如何实现异步编程? 中的浅谈Promise如何实现异步执行小节 ...

随机推荐

  1. linux文件校验

    最近在一次安装centos7程序中遇到了网速很卡的情况,不得已采用了百度云的离线下载功能,后来上传进入虚拟机内,结果遇到无法上传的情况,后来经过转码后才上传成功,详情http://www.cnblog ...

  2. Nginx 文件传输效率、实时、压缩配置指令

    # sendfile 开启文件高效传输模式 # 默认值:off # 位置:http.servcer.location-- # 开启和不开启worker访问的文件发送到浏览器的过程不同. # 不开启的时 ...

  3. Golang 汇编asm语言基础学习

    Golang 汇编asm语言基础学习 一.CPU 基础知识 cpu 内部结构 cpu 内部主要是由寄存器.控制器.运算器和时钟四个部分组成. 寄存器:用来暂时存放指令.数据等对象.它是一个更快的内存. ...

  4. springboot整合xxl-job分布式定时任务【图文完整版】

    一.前言 定时任务有很多种,有一些大的框架也有一些简单的实现. 比如常见的: JDK的Timer和TimerTask Quartz异步任务调度框架 分布式定时任务XXL-JOB Spring Task ...

  5. iNeuOS工业互联网操作系统,在航天和军工测控领域的应用

    目       录 1.      行业概述... 2 2.      解决方案... 2 3.      解决的痛点... 6 1.   行业概述 现在国际形势异常严峻,加大了偶发武装斗争的可能性. ...

  6. Java反射原理和实际用法

    背景 反射在Java中非常重要,是Java区别于其他编程语言的一大特性.Java中的AOP切面.动态代理等看起来像黑魔法一样的技术,就离不开反射.字节码等.这些技术能在不侵入原有代码的情况下,做一些增 ...

  7. Redis 15 主从复制

    参考源 https://www.bilibili.com/video/BV1S54y1R7SB?spm_id_from=333.999.0.0 版本 本文章基于 Redis 6.2.6 概述 主从复制 ...

  8. 业界压测平台与JMeter的对比

    压测平台是什么? 压测,即压力测试,作用是对各种服务对象进行压力测试以获得该服务处于或超过预期负载时系统的运行情况,进而判断系统在峰值负载或超出最大负载情况下的处理能力. 压测工具,顾名思义,就是用来 ...

  9. Java-文件File简单实用

    1.1java.io.File File用于表示文件系统中的一个文件或目录 通过File可以: 1:访问该文件或目录的属性信息(名字,大小,修改时间等) file.getName();获取文件名 fi ...

  10. js运算符和逻辑分支

    运算符 1.拼接运算符:+,加号两边只要有一边出现字符串就是拼接 2.算术运算符  如:2+3: 3.赋值运算符+=,-=,/=,*= 4.关系运算符>,<,==,=== != !== ! ...