flink 1.9
本页阐述了使用Flink的API来进行外部数据存储的异步I/O,对于不熟悉异步或者事件驱动编程的用户,学习一篇关于Future和事件驱动编程可能会很有用。
注意:关于异步I/O的详细设计和实现可以在异步I/O设计和实现这篇文章找到(FLIP-12: Asynchronous I/O Design and Implementation)。
The need for Asynchronous I/O Operations
在与外部系统交互时(例如,使用存储在数据库中的数据流事件),外部系统通信的延迟不应该堵塞流应用程序的其他处理工作,否则会影响流处理的吞吐量。
原始的访问外部系统中的数据,例如对于MapFunction类,通常是同步交互synchronous :将一个资源请求发送到数据库,MapFunction将会一直等待资源,直到接收到响应为止。通常这种情况下,资源的等待会占用函数执行的绝大部分时间。
与数据库的异步交互意味着一个并行函数实例可以同时处理多个请求任务,并同时接收多个响应任务。这样,等待的时间就可以被其他的请求和接收响应所占用,以便处理其他请求任务,从而提高流处理的吞吐量。
注:改进吞吐量只有增加MapFunction并行度,这在某些情况下是可能的,但通常存在非常高的资源成本:有更多的并行MapFunction实例意味着更多的任务,线程,Flink-internal网络连接,网络连接到数据库,缓冲区,和一般内部记帐开销。
Prerequisites前提
(1)首先需要一个客户端,数据库支持通过该客户端来进行异步调用请求,目前流行的数据库基本上都存在这样的一个客户端。
(2)对于没有这种客户端的数据库,用户可以通过创建多个客户端,然后使用线程池同步调用这些同步客户端,从而将其转换为有限的并发客户端。然而,这个方法通常比纯粹的异步客户端性能要低一些。
异步I/O API
Flink的Async I/O允许用户在数据流中使用异步的请求客户端,这个API会处理数据流间的交互行为,同时还处理数据的顺序、事件时间、容错等。
假标数据库已经具有异步客户端,要实现一个通过异步I/O来操作数据库的应用还需要三步:
1、实现一个用来分发请求的AsyncFunction类;
2、获取操作结果并将其传递给ResultFuture的回调callback函数;
3、将异步async I/O操作作为转换操作应用到DataStream中。
下面代码展示了这个基本模式:
// This example implements the asynchronous request and callback with Futures that have the
// interface of Java 8's futures (which is the same one followed by Flink's Future)
/**
* An implementation of the 'AsyncFunction' that sends requests and sets the callback.
*/
class AsyncDatabaseRequest extends RichAsyncFunction> {
/** The database specific client that can issue concurrent requests with callbacks */
private transient DatabaseClient client;
@Override
public void open(Configuration parameters) throws Exception {
client = new DatabaseClient(host, post, credentials);
}
@Override
public void close() throws Exception {
client.close();
}
@Override
public void asyncInvoke(String key, final ResultFuture> resultFuture) throws Exception {
// issue the asynchronous request, receive a future for result
final Future result = client.query(key);
// set the callback to be executed once the request by the client is complete
// the callback simply forwards the result to the result future
CompletableFuture.supplyAsync(new Supplier() {
@Override
public String get() {
try {
return result.get();
} catch (InterruptedException | ExecutionException e) {
// Normally handled explicitly.
return null;
}
}
}).thenAccept( (String dbResult) -> {
resultFuture.complete(Collections.singleton(new Tuple2<>(key, dbResult)));
});
}
}
// create the original stream
DataStream stream = ...;
// apply the async I/O transformation
DataStream> resultStream =
AsyncDataStream.unorderedWait(stream, new AsyncDatabaseRequest(), 1000, TimeUnit.MILLISECONDS, 100);
注意:ResultFuture是在ResultFuture.complete的第一次调用中完成的。所有后续的complete 调用都将被忽略。
下面的两个参数控制了异步操作:
Timeout Handling
当异步I/O请求超时时,默认情况下会引发异常并重新启动作业。如果要处理超时,可以重写AsyncFunction#timeout方法。
Order of Results
由AsyncFunction发出的并发请求经常是以无序的形式完成,取决于哪个请求先完成。为了控制发出请求结果的顺序,Flink提供了两种模式:
Event Time
当流应用程序处理事件时间event time时,异步I/O操作算子将正确处理水印。具体地说,这两种有序模式是:
这意味着,在存在水印的情况下,无序模式引入了一些与有序模式相同的延迟和管理开销。该开销的大小取决于水印的频率。
请记住,插入Ingestion时间是事件时间的特殊情况,它根据源处理时间自动生成水印。
Fault Tolerance Guarantees
异步I/O操作提供了exactly-once容错性保证,它将异步请求的记录存储在checkpoint中,当从故障中恢复时,将会从checkpoint中恢复和重新触发异步请求。
使用提醒Implementation Tips
对于Futures 的实现使其具有用于回调的org.apache.flink.runtime.concurrent.Executors(或Scala中的ExecutionContext)类,我们建议使用DirectExecutor,因为回调通常只做很少的工作,而DirectExecutor可以避免额外的线程到线程切换开销。回调通常只将结果提交给ResultFuture,后者将其添加到输出缓冲区。从这里开始,包含记录发送和与检查点保存记录的交互的繁重逻辑将在专用线程池中进行。
public class CompletableFuture implements Future, CompletionStage {}
DirectExecutor可以通过org.apache.flink.runtime.concurrent.Executors.directExecutor()或com.google.common.util.concurrent.MoreExecutors.directExecutor()获得。
The AsyncFunction is not called Multi-Threaded
这里我们要明确指出的一个常见容易混淆的概念:AsyncFunction不是以多线程方式的进行调用的。运行过程中,AsyncFunction只存在一个实例,并且对于流的各个分区中的每个记录,它都会被依次调用。除非asyncInvoke(…)方法能够快速返回和依赖一个回调callback(通过客户端回调),否则它将不会产生正确的异步I/O。
例如,以下模式将会导致asyncInvoke(…)函数阻塞,从而使异步行为无效:
由于一致性的原因,AsyncFunction操作算子(AsyncWaitOperator)当前必须位于操作算子链的前端
由于问题FLINK-13063中给出的原因,我们目前必须打破AsyncWaitOperator的操作算子链,以防止潜在的一致性问题。这是对以前支持链接的行为的一个更改。需要旧行为并接受可能违反一致性保证的用户可以实例化AsyncWaitOperator并手动将其添加到作业图中,并将链接策略设置回通过AsyncWaitOperator#setChainingStrategy(ChainingStrategy.ALWAYS)
https://www.jianshu.com/p/a04f9ea5fe3d
https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/stream/operators/asyncio.html