对于异步 I/O 操作的需求
在与外部系统交互(用数据库中的数据扩充流数据)的时候,需要考虑与外部系统的通信延迟对整个流处理应用的影响。
简单地访问外部数据库的数据,比如使用 MapFunction,通常意味着同步交互: MapFunction 向数据库发送一个请求然后一直等待,直到收到响应。在许多情况下,等待占据了函数运行的大部分时间。
与数据库异步交互是指一个并行函数实例可以并发地处理多个请求和接收多个响应。这样,函数在等待的时间可以发送其他请求和接收其他响应。至少等待的时间可以被多个请求摊分。大多数情况下,异步交互可以大幅度提高流处理的吞吐量。
注意: 仅仅提高 MapFunction 的并行度(parallelism)在有些情况下也可以提升吞吐量,但是这样做通常会导致非常高的资源消耗:更多的并行 MapFunction 实例意味着更多的 Task、更多的线程、更多的 Flink 内部网络连接、 更多的与数据库的网络连接、更多的缓冲和更多程序内部协调的开销。
先决条件
如上节所述,正确地实现数据库(或键/值存储)的异步 I/O 交互需要支持异步请求的数据库客户端。许多主流数据库都提供了这样的客户端。
如果没有这样的客户端,可以通过创建多个客户端并使用线程池处理同步调用的方法,将同步客户端转换为有限并发的客户端。然而,这种方法通常比正规的异步客户端效率低。
异步 I/O API
Flink 的异步 I/O API 允许用户在流处理中使用异步请求客户端。API 处理与数据流的集成,同时还能处理好顺序、事件时间和容错等。
在具备异步数据库客户端的基础上,实现数据流转换操作与数据库的异步 I/O 交互需要以下三部分:
实现分发请求的 AsyncFunction
获取数据库交互的结果并发送给 ResultFuture 的 回调 函数
将异步 I/O 操作应用于 DataStream 作为 DataStream 的一次转换操作。
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.api.java.tuple.Tuple5;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.functions.async.ResultFuture;
import org.apache.flink.streaming.api.functions.async.RichAsyncFunction;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
// TODO: 2022/2/17 实现 'RichAsyncFunction' 用于发送请求和设置回调。传入参数 1.输入流的类型 2.输出流的类型
public class AsyncMysqlData extends RichAsyncFunction<Tuple2<Integer, Long>, Tuple5<Integer,Long,Integer,String,String>> {
//引入mysql客户端
private transient Connection client;
//配置异步数据源的参数
@Override
public void open(Configuration parameters) throws Exception {
Class.forName("com.mysql.cj.jdbc.Driver");
client = DriverManager.getConnection("jdbc:mysql://localhost:3306/school?useSSL=false&serverTimezone=UTC", "root", "1234");
client.setAutoCommit(false);
}
//关闭客户端
@Override
public void close() throws Exception {
client.close();
}
//异步调用把数据存入队列中
@Override
public void asyncInvoke(Tuple2<Integer, Long> input, ResultFuture<Tuple5<Integer, Long, Integer, String, String>> resultFuture) throws Exception {
//创建list集合保存异步缓存的数据
List<Tuple5<Integer, Long, Integer, String, String>> list = new ArrayList<>();
Statement statement = client.createStatement();
ResultSet resultSet = statement.executeQuery("select pid, pname, psex, page from person where page= " +input.f0 );
if (resultSet != null && resultSet.next()) {
String name = resultSet.getString("pname");
int pid = resultSet.getInt("pid");
String psex = resultSet.getString("psex");
Tuple5<Integer, Long, Integer, String, String> res = Tuple5.of(input.f0, input.f1, pid,name,psex);
list.add(res);
}
// 将数据搜集
resultFuture.complete(list);
}
主函数调用
import com.zxl.blink.StudentDB;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.api.java.tuple.Tuple5;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.configuration.RestOptions;
import org.apache.flink.streaming.api.datastream.AsyncDataStream;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import java.util.concurrent.TimeUnit;
public class AsyncDemo {
public static void main(String[] args) throws Exception {
//配置FLINK WEB UI 可以登入localhost:8848 查看flink运行图
Configuration configuration = new Configuration();
configuration.setInteger(RestOptions.PORT,8848);
//创建执行环境
StreamExecutionEnvironment environment = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(configuration);
environment.setParallelism(4);
//调用自定义函数形成数据流
DataStream<Tuple2<Integer, Long>> studentSource = environment.addSource(new StudentDB());
// TODO: 2022/2/17 调用 AsyncDataStream异步流方法unorderedWait无序,orderedWait有序
//主流
//实现异步函数RichAsyncFunction的流
//Timeout: 超时参数定义了异步请求发出多久后未得到响应即被认定为失败。 它可以防止一直等待得不到响应的请求。
//TimeUnit: 时间类型
//Capacity: 容量参数定义了可以同时进行的异步请求数。 即使异步 I/O 通常带来更高的吞吐量,执行异步 I/O 操作的算子仍然可能成为流处理的瓶颈。 限制并发请求的数量可以确保算子不会持续累积待处理的请求进而造成积压,而是在容量耗尽时触发反压。
DataStream<Tuple5<Integer, Long, Integer, String, String>> operator = AsyncDataStream.unorderedWait(studentSource, new AsyncMysqlData(), 1000, TimeUnit.MILLISECONDS, 100);
//打印数据流
operator.print("asyncStream");
//执行任务
environment.execute();
}
}