Flink中异步AsyncIO的实现 (源码分析)
先上张图整体了解Flink中的异步io
阿里贡献给flink的,优点就不说了嘛,官网上都有,就是写库不会柱塞性能更好
然后来看一下, Flink 中异步io主要分为两种
一种是有序Ordered
一种是无序UNordered
主要区别是往下游output的顺序(注意这里顺序不是写库的顺序既然都异步了写库的顺序自然是无法保证的),有序的会按接收的顺序继续往下游output发送,无序就是谁先处理完谁就先往下游发送
两张图了解这两种模式的实现
有序:record数据会通过异步线程写库,Emitter是一个守护进程,会不停的拉取queue头部的数据,如果头部的数据异步写库完成,Emitter将头数据往下游发送,如果头元素还没有异步写库完成,柱塞
无序:record数据会通过异步线程写库,这里有两个queue,一开始放在uncompleteedQueue,当哪个record异步写库成功后就直接放到completedQueue中,Emitter是一个守护进程,completedQueue只要有数据,会不停的拉取queue数据往下游发送
可以看到原理还是很简单的,两句话就总结完了,就是利用queue和java的异步线程,现在来看下源码
这里AsyncIO在Flink中被设计成operator中的一种,自然去OneInputStreamOperator的实现类中去找
于是来看一下AsyncWaitOperator.java
看到它的open方法(open方法会在taskmanager启动job的时候全部统一调用,可以翻一下以前的文章)
这里启动了一个守护线程Emitter,来看下线程具体做了什么
1处拉取数据,2处就是常规的将拉取到的数据往下游emit,Emitter拉取数据,这里先不讲因为分为有序的和无序的
这里已经知道了这个Emitter的作用是循环的拉取数据往下游发送
回到AsyncWaitOperator.java在它的open方法初始化了Emitter,那它是如何处理接收到的数据的呢,看它的ProcessElement()方法
其实主要就是三个个方法
先是!!!将record封装成了一个包装类StreamRecordQueueEntry,主要是这个包装类的构造方法中,创建了一个CompleteableFuture(这个的complete方法其实会等到用户代码执行的时候用户自己决定什么时候完成)
1处主要就是讲元素加入到了对应的queue,这里也分为两种有序和无序的
这里也先不讲这两种模式加入数据的区别
接着2处就是调用用户的代码了,来看看官网的异步io的例子
给了一个Future作为参数,用户自己起了一个线程(这里思考一下就知道了为什么要新起一个异步线程去执行,因为如果不起线程的话,那processElement方法就柱塞了,无法异步了)去写库读库等,然后调用了这个参数的complete方法(也就是前面那个包装类中的CompleteableFuture)并且传入了一个结果
看下complete方法源码
这个resultFuture是每个record的包装类StreamRecordQueueEntry的其中一个属性是一个CompletableFuture
那现在就清楚了,用户代码在自己新起的线程中当自己的逻辑执行完以后会使这个异步线程结束,并输入一个结果
那这个干嘛用的呢
最开始的图中看到有序和无序实现原理,有序用一个queue,无序用两个queue分别就对应了
OrderedStreamElementQueue类中
UnorderedStreamElementQueue类中
回到前面有两个地方没有细讲,一是两种模式的Emitter是如何拉取数据的,二是两种模式下数据是如何加入OrderedStreamElementQueue的
有序模式:
1.先来看一下有序模式的,Emitter的数据拉取,和数据的加入
其tryPut()方法
onComplete方法
onCompleteHandler方法
这里比较绕,先将接收的数据加入queue中,然后onComplete()中当上一个异步线程getFuture() 其实就是每个元素包装类里面的那个CompletableFuture,当他结束时(会在用户方法用户调用complete时结束)异步调用传入的对象的 accept方法,accept方法中调用了onCompleteHandler()方法,onCompleteHandler方法中会判断queue是否为空,以及queue的头元素是否完成了用户的异步方法,当完成的时候,就会将headIsCompleted这个对象signalAll()唤醒
2.接着看有序模式Emitter的拉取数据
这里有序方式拉取数据的逻辑很清晰,如果为空或者头元素没有完成用户的异步方法,headIsCompleted这个对象会wait住(上面可以知道,当加入元素的到queue且头元素完成异步方法的时候会signalAll())然后将头数据返回,往下游发送
这样就实现了有序发送,因为Emitter只拉取头元素且已经完成用户异步方法的头元素
无序模式:
这里和有序模式就大同小异了,只是变成了,接收数据后直接加入uncompletedQueue,当数据完成异步方法的时候就,放到completedQueue里面去并signalAll(),只要completedqueue里面有数据,Emitter就拉取往下发
这样就实现了无序模式,也就是异步写入谁先处理完就直接放到完成队列里面去,然后往下发,不用管接收数据的顺序