Flink编程模型与API

异步IO机制原理

Flink的异步I/O是一个非常受欢迎的特性,由阿里巴巴贡献给社区,并在1.2版本中引入,它的主要目的是解决与外部系统交互时网络延迟成为系统瓶颈的问题,外部系统往往是外部数据库。

在Flink流计算系统中,与外部数据库进行交互是常见的需求,通常情况下,我们会发送一个查询请求到数据库并等待结果返回,这期间无法发送其他请求,这种同步访问方式会导致阻塞,阻碍了吞吐量和延迟,为了解决这个问题,引入了异步模式,能够并发地处理多个到外部数据库的请求,下图为官方提供的异步IO原理图。

在Flink中使用异步I/O,我们可以连续发送多个查询请求到数据库,并在回复返回时处理每个回复,而不需要阻塞等待,这种并发处理的方式极大地减少了延迟。

异步I/O专门用于解决Flink计算过程中与外部系统的交互问题,特别需要注意的是为了提高Flink与外部系统交互能力,也可以提高Flink的并行度进而提高Flink处理数据的吞吐量,这种方式会付出更高的资源成本,如:更多的task、更多的内存缓存、更高的网络连接,而异步IO方式相对这种方式是基于某一个task之上的扩展,重复利用一个task资源做更多的事情,提高了Flink性能和资源利用率。

异步IO使用前提

在Flink中查询外界数据库数据时要使用异步IO需要满足如下条件之一:

数据库(或K/V存储系统)提供支持异步请求的客户端,例如Java的Vertx。

对于不支持异步请求客户端的外部系统可以使用线程池模拟异步客户端。

注意:Java Vertx是一个基于JVM的应用平台,适用于移动端后台、互联网和企业应用架构,采用了基于Netty的全异步通信,支持多种语言,目前Vertx的异步驱动已经支持了Postgres、MySQL、MongoDB、Redis等常用组件,可以作为异步连接这些数据库的工具。

异步IO代码

数据准备
异步请求客户端
java 复制代码
yi/**
 *  实现Flink异步IO方式一:使用 Vert.x 实现异步 IO
 *  案例:读取MySQL中的数据
 */
public class AsyncIOTest1 {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        //为了测试效果,这里设置并行度为1
        env.setParallelism(1);
        //准备数据流
        DataStreamSource<Integer> idDS = env.fromCollection(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));

        /**
         * 使用异步IO,参数解释如下:
         *  第一个参数是输入数据流,
         *  第二个参数是异步IO的实现类,
         *  第三个参数是用于完成异步操作超时时间,
         *  第四个参数是超时时间单位,
         *  第五个参数可以触发的最大异步i/o操作数
         */
        AsyncDataStream.unorderedWait(idDS, new AsyncDatabaseRequest1(), 5000, TimeUnit.MILLISECONDS, 10)
                .print();

        env.execute();

    }

}

class AsyncDatabaseRequest1 extends RichAsyncFunction<Integer, String> {
    //定义JDBCClient共享对象
    JDBCClient mysqlClient = null;

    //初始化资源,连接Mysql
    @Override
    public void open(Configuration parameters) throws Exception {
        //创建连接mysql配置对象
        JsonObject config = new JsonObject()
                .put("url", "jdbc:mysql://node2:3306/mydb?useSSL=false")
                .put("driver_class", "com.mysql.jdbc.Driver")
                .put("user", "root")
                .put("password", "123456");

        //创建VertxOptions对象
        VertxOptions vo = new VertxOptions();
        //设置Vertx要使用的事件循环线程数
        vo.setEventLoopPoolSize(10);
        //设置Vertx要使用的最大工作线程数
        vo.setWorkerPoolSize(20);

        //创建Vertx对象
        Vertx vertx = Vertx.vertx(vo);

        //创建JDBCClient共享对象,多个Vertx 客户端可以共享一个JDBCClient对象
        mysqlClient = JDBCClient.createShared(vertx, config);
    }

    //实现异步IO的方法,第一个参数是输入,第二个参数是异步IO返回的结果
    @Override
    public void asyncInvoke(Integer input, ResultFuture<String> resultFuture) {
        mysqlClient.getConnection(new Handler<AsyncResult<SQLConnection>>() {
            @Override
            public void handle(AsyncResult<SQLConnection> sqlConnectionAsyncResult) {
                if (sqlConnectionAsyncResult.failed()) {
                    System.out.println("获取连接失败:" + sqlConnectionAsyncResult.cause().getMessage());
                    return;
                }

                //获取连接
                SQLConnection connection = sqlConnectionAsyncResult.result();

                //执行查询
                connection.query("select id,name,age from async_tbl where id = " + input, new Handler<AsyncResult<io.vertx.ext.sql.ResultSet>>() {
                    @Override
                    public void handle(AsyncResult<io.vertx.ext.sql.ResultSet> resultSetAsyncResult) {
                        if (resultSetAsyncResult.failed()) {
                            System.out.println("查询失败:" + resultSetAsyncResult.cause().getMessage());
                            return;
                        }

                        //获取查询结果
                        io.vertx.ext.sql.ResultSet resultSet = resultSetAsyncResult.result();

                        //打印查询的结果
                        //将查询结果返回给Flink
                        resultSet.getRows().forEach(row -> {
                            resultFuture.complete(Collections.singletonList(row.encode()));
                        });
                    }
                });
            }
        });
    }


    /**
     * 异步IO超时处理逻辑,主要避免程序出错。参数如下:
     * 第一个参数是输入数据
     * 第二个参数是异步IO返回的结果
     */
    @Override
    public void timeout(Integer input, ResultFuture<String> resultFuture) throws Exception {
        resultFuture.complete(Collections.singletonList("异步IO超时!!!"));
    }

    //关闭资源
    @Override
    public void close() throws Exception {
        mysqlClient.close();
    }
}

Scala代码实现:

java 复制代码
/**
 * 实现Flink异步IO方式一:使用 Vert.x 实现异步 IO
 * 案例:读取MySQL中的数据
 */
object AsyncIOTest1 {
  def main(args: Array[String]): Unit = {
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    //为了测试效果,这里设置并行度为1
    env.setParallelism(1)

    //导入隐式转换
    import org.apache.flink.streaming.api.scala._

    //准备数据流
    val idDS: DataStream[Int] = env.fromCollection(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))

    AsyncDataStream.unorderedWait(idDS, new AsyncDatabaseRequest1(), 5000, java.util.concurrent.TimeUnit.MILLISECONDS, 10)
        .print()

    env.execute()
  }

}

class AsyncDatabaseRequest1 extends RichAsyncFunction[Int,String]() {

  //定义JDBCClient对象
  var mysqlClient: JDBCClient = null

  //初始化资源,连接MySQL
  override def open(parameters: Configuration): Unit = {
    //创建连接MySQL的配置信息
    val config: JsonObject = new JsonObject()
      .put("url", "jdbc:mysql://node2:3306/mydb?useSSL=false")
      .put("driver_class", "com.mysql.jdbc.Driver")
      .put("user", "root")
      .put("password", "123456")

    //创建VertxOptions对象
    val vo = new VertxOptions()
    //设置Vertx要使用的事件循环线程数
    vo.setEventLoopPoolSize(10)
    //设置Vertx要使用的最大工作线程数
    vo.setWorkerPoolSize(20)

    //创建Vertx对象
    val vertx = io.vertx.core.Vertx.vertx(vo)
    //创建 JDBCClient 共享对象,多个Vertx客户端可以共享一个JDBCClient实例
    mysqlClient = JDBCClient.createShared(vertx, config)
  }

  //实现异步IO的方法,第一个参数是输入,第二个参数是异步IO返回的结果
  override def asyncInvoke(input: Int, resultFuture: ResultFuture[String]): Unit = {
    //获取MySQL连接
    mysqlClient.getConnection(new Handler[AsyncResult[SQLConnection]] {
      override def handle(sqlConnectionAsyncResult: AsyncResult[SQLConnection]): Unit = {
        if(!sqlConnectionAsyncResult.failed()){
          //获取连接
          val connection : SQLConnection = sqlConnectionAsyncResult.result()

          //执行查询
          connection.query("select id,name,age from async_tbl where id = " + input,new Handler[AsyncResult[ResultSet]] {
            override def handle(resultSetAsyncResult: AsyncResult[ResultSet]): Unit = {
              if(!resultSetAsyncResult.failed()){
                //获取查询结果
                val resultSet: ResultSet = resultSetAsyncResult.result()
                resultSet.getRows().asScala.foreach(row=>{
                  //返回结果
                  resultFuture.complete(List(row.encode()))
                })
              }
            }
          })
        }
      }
    })

  }

  /**
   * 异步IO超时处理逻辑,主要避免程序出错。参数如下:
   * @param input 输入数据
   * @param resultFuture 异步IO返回的结果
   */
  override def timeout(input: Int, resultFuture: ResultFuture[String]): Unit = {
    resultFuture.complete(List("异步IO超时!!!"))
  }

  //关闭资源
  override def close(): Unit = {
    mysqlClient.close() //关闭连接
  }
}
线程池模拟异步请求客户端
java 复制代码
/**
 * 实现Flink异步IO方式二:线程池模拟异步客户端
 * 案例:读取MySQL中的数据
 */
public class AsyncIOTest2 {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        //为了测试效果,这里设置并行度为1
        env.setParallelism(1);
        //准备数据流
        DataStreamSource<Integer> idDS = env.fromCollection(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));

        /**
         * 使用异步IO,参数解释如下:
         *  第一个参数是输入数据流,
         *  第二个参数是异步IO的实现类,
         *  第三个参数是用于完成异步操作超时时间,
         *  第四个参数是超时时间单位,
         *  第五个参数可以触发的最大异步i/o操作数
         */
        AsyncDataStream.unorderedWait(idDS, new AsyncDatabaseRequest2(), 5000, TimeUnit.MILLISECONDS, 10)
                .print();

        env.execute();

    }
}

class AsyncDatabaseRequest2 extends RichAsyncFunction<Integer, String> {

    //准备线程池对象
    ExecutorService executorService = null;

    //初始化资源,这里主要是初始化线程池
    @Override
    public void open(Configuration parameters) throws Exception {
        //初始化线程池,第一个参数是线程池中线程的数量,第二个参数是线程池中线程的最大数量,第三个参数是线程池中线程空闲的时间,第四个参数是线程池中线程空闲时间的单位,第五个参数是线程池中的任务队列
        executorService = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());
    }

    @Override
    public void asyncInvoke(Integer input, ResultFuture<String> resultFuture) throws Exception {
        //提交异步任务到线程池中
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    /**
                     * 以下两个方法不能设置在open方法中,因为多线程共用数据库连接和pst对象,这样会导致线程不安全
                     */
                    Connection conn = DriverManager.getConnection("jdbc:mysql://node2:3306/mydb?useSSL=false", "root", "123456");
                    PreparedStatement pst = conn.prepareStatement("select id,name,age from async_tbl where id = ?");
                    //设置参数
                    pst.setInt(1, input);
                    //执行查询并获取结果
                    ResultSet resultSet = pst.executeQuery();
                    //遍历结果集
                    while (resultSet != null && resultSet.next()) {
                        //获取数据
                        int id = resultSet.getInt("id");
                        String name = resultSet.getString("name");
                        int age = resultSet.getInt("age");
                        //返回结果
                        resultFuture.complete(Arrays.asList("id="+id+",name="+name+",age="+age));
                    }

                    //关闭资源
                    pst.close();
                    conn.close();

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

    }

    /**
     * 异步IO超时处理逻辑,主要避免程序出错。参数如下:
     * 第一个参数是输入数据
     * 第二个参数是异步IO返回的结果
     */
    @Override
    public void timeout(Integer input, ResultFuture<String> resultFuture) throws Exception {
        resultFuture.complete(Collections.singletonList("异步IO超时!!!"));
    }

    //关闭资源
    @Override
    public void close() throws Exception {
        //关闭线程池
        executorService.shutdown();
    }
}
java 复制代码
/**
 * 实现Flink异步IO方式二:线程池模拟异步客户端
 * 案例:读取MySQL中的数据
 */
object AsyncIOTest2 {
  def main(args: Array[String]): Unit = {
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    //为了测试效果,这里设置并行度为1
    env.setParallelism(1)

    //导入隐式转换
    import org.apache.flink.streaming.api.scala._

    //准备数据流
    val idDS: DataStream[Int] = env.fromCollection(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))

    /**
     * 使用异步IO,参数解释如下:
     *  第一个参数是输入数据流,
     *  第二个参数是异步IO的实现类,
     *  第三个参数是用于完成异步操作超时时间,
     *  第四个参数是超时时间单位,
     *  第五个参数可以触发的最大异步i/o操作数
     */
    AsyncDataStream.unorderedWait(idDS, new AsyncDatabaseRequest2(), 5000, java.util.concurrent.TimeUnit.MILLISECONDS, 10)
      .print()

    env.execute()
  }

}

class AsyncDatabaseRequest2 extends RichAsyncFunction[Int,String]() {
  //准备线程池对象
  var executorService: ExecutorService = null


  //初始化资源,准备线程池
  override def open(parameters: Configuration): Unit = {
    //初始化线程池,第一个参数是线程池中线程的数量,第二个参数是线程池中线程的最大数量,第三个参数是线程池中线程空闲的时间,第四个参数是线程池中线程空闲时间的单位,第五个参数是线程池中的任务队列
    executorService = new ThreadPoolExecutor(10,10,0L,java.util.concurrent.TimeUnit.MILLISECONDS,
      new java.util.concurrent.LinkedBlockingQueue[Runnable]())
  }

  //多线程方式处理数据
  override def asyncInvoke(input: Int, resultFuture: ResultFuture[String]): Unit = {
    //使用线程池执行异步任务
    executorService.submit(new Runnable {
      override def run(): Unit = {
        /**
         * 以下两个方法不能设置在open方法中,因为多线程共用数据库连接和pst对象,这样会导致线程不安全
         */
        val conn: Connection = DriverManager.getConnection("jdbc:mysql://node2:3306/mydb?useSSL=false", "root", "123456")
        val pst: PreparedStatement = conn.prepareStatement("select id,name,age from async_tbl where id = ?")

        //设置参数
        pst.setInt(1, input)
        //执行查询并获取结果
        val rs = pst.executeQuery()
        while(rs!=null && rs.next()){
          val id: Int = rs.getInt("id")
          val name: String = rs.getString("name")
          val age: Int = rs.getInt("age")
          //将结果返回给Flink
          resultFuture.complete(List("id = "+id+",name = "+name+",age = "+age))
        }

        //关闭资源
        pst.close();
        conn.close();

      }
    })
  }

  /**
   * 异步IO超时处理逻辑,主要避免程序出错。参数如下:
   * 第一个参数是输入数据
   * 第二个参数是异步IO返回的结果
   */
  override def timeout(input: Int, resultFuture: ResultFuture[String]): Unit = {
    resultFuture.complete(List("异步IO超时了!!!"))
  }

  //关闭资源
  override def close(): Unit = {
    //关闭线程池
    executorService.shutdown()
  }
}
相关推荐
金融Tech趋势派2 小时前
食品连锁品牌私域运营:企业微信+微盛·企微管家AI SCRM打造降本提效闭环
大数据·人工智能·企业微信
清辞8533 小时前
入门大模型工程师第四课----通过RAG增强大模型原本无法回答的问题
大数据·人工智能·学习·语言模型
科技互联.3 小时前
2026 数据治理中台选型指南:开放集成与 AI 智能化成为采购核心评判标准
大数据·人工智能
AI大法师3 小时前
奥迪 AUDI 案例:母品牌和新业务怎么拆?
大数据·设计模式·汽车
川石课堂软件测试4 小时前
性能测试|JMeter常用线程组设置策略
大数据·数据库·功能测试·测试工具·jmeter·mysql·单元测试
Kyligence4 小时前
被低估的数据底座,正在决定 AI 时代智能应用的上限
大数据·人工智能
真上帝的左手4 小时前
19. 大数据- BI 入门-数仓实战1-数据仓库的核心逻辑与落地范式
大数据·数据仓库·bi
chatexcel4 小时前
ChatExcel Max升级体验:从表格处理到企业级业务数据分析
大数据·人工智能·数据分析
腾视科技AI4 小时前
AI赋能 车行无忧|腾视科技ES10车载智能终端,为车辆装上“智慧大脑”
大数据·人工智能·科技·ai·边缘计算·车载终端·车载智能终端