1 Aysnc I/O是啥?
Async I/O 是阿里巴巴贡献给社区的一个呼声非常高的特性,于1.2版本引入。
主要目的:是为了解决与外部系统交互时网络延迟成为了系统瓶颈的问题。
场景:
流计算系统中经常需要与外部系统进行交互,比如需要查询外部数据库以关联上用户的额外信息。通常,我们的实现方式是向数据库发送用户a的查询请求(例如在MapFunction中),然后等待结果返回,在这之前,我们无法发送用户b的查询请求。这是一种同步访问的模式,如下图左边所示。
图中棕色的长条表示等待时间,可以发现网络等待时间极大地阻碍了吞吐和延迟。为了解决同步访问的问题,异步模式可以并发地处理多个请求和回复。也就是说,你可以连续地向数据库发送用户a、b、c等的请求,与此同时,哪个请求的回复先返回了就处理哪个回复,从而连续的请求之间不需要阻塞等待,如上图右边所示。这也正是 Async I/O 的实现原理。
2 提高吞吐量的两种方式对比
目标:提高吞吐量
3 使用Aysnc I/O的前提条件
1.数据库(或key/value存储)提供支持异步请求的client。
2.没有异步请求客户端的话也可以将同步客户端丢到线程池中执行作为异步客户端。
注意:后者没有前者效果好。
4 Async I/O API实现异步流式转换
Flink Async I/O API 允许用户在数据流中使用异步请求客户端访问外部存储。该API处理与数据流的集成,以及消息顺序性(Order)、事件时间(event time)、一致性(容错)等脏活累活。用户只需要专注于业务。
如果目标数据库有异步客户端,则三步即可实现异步流式转换操作(针对该数据库异步):
1.实现用来分发请求的AsyncFunction ,用来向数据库发送异步请求并设置回调。
2.获取操作结果的callback,并将它提交给ResultFuture。
3.将异步I/O操作应用于DataStream。
5 AsyncFunction
AsyncFunction实现实例如下所示:
6 AsyncFunction超时处理
当异步I/O请求超时时,默认情况下会抛出异常并重新启动job。如果希望处理超时,可以覆盖AsyncFunction的timeout方法。
7 AsyncDataStream工具类
一个工具类,用于将AsyncFunction应用于DataStream。
AsyncFunction发出的并发请求通常是无序的,该顺序基于哪个请求先完成。为了控制结果记录的发出顺序,Flink提供了两种模式,分别对应AsyncDataStream 的两个静态方法,orderedWait 和 unorderedWait:
1.orderedWait(有序):消息的发送顺序与接收到的顺序相同(包括 watermark ),也就是先进先出。
2.unorderedWait(无序) :
a)在 ProcessingTime 的情况下,完全无序,先返回的结果先发送(最低延迟和最低开销)。
b)在 EventTime 的情况下,watermark 不能超越消息,消息也不能超越 watermark,也就是说 watermark 定义的顺序的边界。在两个 watermark 之间的消息的发送是无序的,但是在watermark之后的消息不能先于该watermark之前的消息发送。(这意味着,在存在watermark的情况下,无序模式引入了与有序模式相同的延迟和管理开销。这种开销的大小取决于watermark频率。)
注意:Ingestion Time是一种特殊的Event Time ,它根据source的processing time自动生成watermark。
2个方法的参数都是一样的。参数有:
- DataStream ds:需要通过异步处理的流对象
- AsyncFunction async:实际处理的异步方法,写异步操作逻辑
- long timeout:超时时间(如果超时,则中断异步操作,避免过多的操作连接占用资源,尤其是针对orderedWait)
- TimeUnit timeUnit:超时时间单位
- int capacity:一次触发异步请求的最大数量,默认100。表示同时最多有多少个异步请求在处理,异步IO的方式会导致更高的吞吐量,但是对于实时应用来说该操作也是一个瓶颈。限制并发请求数,算子不会积压过多的未处理请求,但是一旦超过容量的显示会触发背压。该参数可以不配置,但是默认是100。
8 容错与最佳实践
Async I/O operator提供完全exactly-once容错保证,它将运行中的异步请求记录存储在检查点中,并在从故障恢复时恢复/重新触发请求
最佳实践
1.使用Executor作为Future的回调时,推荐使用线程切换开销较小的DirectExecutor,可以选择下面任意方式或得:
org.apache.flink.runtime.concurrent.Executors.directExecutor()
com.google.common.util.concurrent.MoreExecutors.directExecutor()
2.asyncInvoke#asyncInvoke不是被Flink多线程调用的,不要在里面直接使用阻塞操作。