Flink实践: 异步IO

本文深入探讨了Flink在实时处理中引入异步IO的原因,详细解释了异步IO的使用步骤,提供了具体的实现示例,并介绍了结果输出的顺序控制及超时处理方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.为什么需要异步IO

flink在做实时处理时,有时候需要和外部数据交互,但是通常情况下这个交互过程是同步的,这样就会产生大量的等待时间;而异步操作可以在单个函数实例中同时处理多个请求,并且同时接收相应。这样等待时间就平均分摊到了多个请求上,大大减少了请求的等待时长,可以提高实时处理的吞吐量。
flink异步io.png

2.使用flink异步IO的先决条件
  • 需要所连接的数据库支持异步客户端
  • 在没有异步客户端的情况下,可以通过创建多个客户端并使用线程池处理同步调用来尝试将同步客户端转变为有限的并发客户端
3. flink异步IO的使用步骤
  • 实现AsyncFunction接口;
  • 一个回调,该函数取回操作的结果,然后将结果传递给ResultFuture;
  • 在DataStream上应用异步IO操作。
4. 使用示例
import scala.concurrent._
import ExecutionContext.Implicits.global

/**
  * 使用scala并发包的Future模拟一个异步客户端
  */
class DatabaseClient {
  def query: Future[Long] = Future {
    System.currentTimeMillis() / 1000
  }
}

/** 'AsyncFunction' 的一个实现,向数据库发送异步请求并设置回调 
  * 改编自官网实例
  */

class AsyncDatabaseRequest extends AsyncFunction[Int, (Int, Long)] {

  /** The database specific client that can issue concurrent requests with callbacks */
  lazy val client: DatabaseClient = new DatabaseClient

  /** The context used for the future callbacks */
  implicit lazy val executor: ExecutionContext =
    ExecutionContext.fromExecutor(Executors.directExecutor())

  override def asyncInvoke(str: Int,
                           resultFuture: ResultFuture[(Int, Long)]): Unit = {

    // issue the asynchronous request, receive a future for the result
    val resultFutureRequested: Future[Long] = client.query

    // set the callback to be executed once the request by the client is complete
    // the callback simply forwards the result to the result future
    resultFutureRequested.onSuccess {
      case result: Long => resultFuture.complete(Iterable((str, result)))
    }
  }
}

object AsynchronousIOTest {

  def main(args: Array[String]): Unit = {

    val env = StreamExecutionEnvironment.getExecutionEnvironment

    val data: immutable.Seq[Int] = Range(1, 10)

    // 创建数据流
    val dataStream: DataStream[Int] = env.fromCollection(data)

     // 使用异步IO
     val asyn = AsyncDataStream.unorderedWait(
      dataStream,//执行异步操作的DataStream
      new AsyncDatabaseRequest,//
      1000, TimeUnit.MILLISECONDS, //超时时间
      100 // 进行中的异步请求的最大数量
    )

    asyn.print()

    env.execute("AsynchronousIOTest")

  }

}
结果顺序

AsyncDataStream 有两个静态方法,orderedWait 和 unorderedWait,对应了两种输出模式:有序和无序。

  • 有序:消息的发送顺序与接受到的顺序相同(包括 watermark ),也就是先进先出。
  • 无序:
    在 ProcessingTime 的情况下,完全无序,先返回的结果先发送。
    在 EventTime 的情况下,watermark 不能超越消息,消息也不能超越 watermark,也就是说 watermark 定义的顺序的边界。在两个 watermark 之间的消息的发送是无序的,但是在watermark之后的消息不能先于该watermark之前的消息发送。
超时处理

当异步IO请求超时时,默认情况下会引发异常并重新启动作业。如果要处理超时,可以重写AsyncFunction#timeout方法。

  override def timeout(input: Int,
                       resultFuture: ResultFuture[(Int, Long)]): Unit =
    super.timeout(input, resultFuture)

容错保证

异步IO运算符提供完全一次的容错保证。它在检查点中存储正在进行的异步请求的记录,并在从故障中恢复时恢复/重新触发请求。

其他资料

关于flink异步IO的更多信息可以参考flink官网或者Flink 原理与实现:Aysnc I/O这篇文章。

Futures和事件驱动编程的知识可以参考《AKKA入门与实践》这本书第二章的内容:Actor与并发。


参考资料:

https://ci.apache.org/projects/flink/flink-docs-release-1.8/dev/stream/operators/asyncio.html
http://wuchong.me/blog/2017/05/17/flink-internals-async-io/
《AKKA入门与实践》

### 如何在 Flink SQL 中实现异步 IO 操作 为了在 Flink SQL 中利用异步 IO 提升性能,通常会借助 `AsyncFunction` 或者更具体的 `RichAsyncFunction` 来完成对外部系统的非阻塞调用。虽然直接在 SQL 查询语句里无法声明这些 Java/Scala 函数,但是可以通过创建自定义函数的方式间接支持这一特性。 当涉及到具体的应用场景时,例如从数据库或其他服务获取额外的数据来增强当前记录的信息,则可以在 DataStream API 层面先应用异步逻辑再转换成表或者视图供后续的 SQL 处理使用[^1]。 对于希望完全基于 SQL 实现的需求来说,官方推荐的做法是通过 UDF (User Defined Function) 将异步功能封装起来,在 SQL 中作为普通的标量函数调用。下面是一个简单的例子展示如何构建这样的环境: #### 创建异步函数类 ```java import org.apache.flink.streaming.api.functions.async.ResultFuture; import org.apache.flink.table.functions.AsyncTableFunction; import java.util.concurrent.CompletableFuture; public class AsyncLookup extends AsyncTableFunction<String> { @Override public void eval(String key, ResultFuture<String> resultFuture) { // 这里的 lookupService 是假设存在的用于执行实际网络请求的服务接口 CompletableFuture.supplyAsync(() -> lookupService.get(key)) .thenAccept(resultFuture::complete); } } ``` 此代码片段展示了如何编写一个继承自 `AsyncTableFunction` 的类,并重写其 `eval()` 方法以启动异步任务。注意这里使用的 `CompletableFuture` 可以让程序不必等待 I/O 完成就能继续向下运行其他指令[^3]。 #### 注册并使用自定义异步函数 一旦完成了上述步骤之后,就可以像注册普通UDF那样把这个新的组件加入到环境中去: ```sql CREATE FUNCTION async_lookup AS 'com.example.AsyncLookup'; SELECT id, name, (async_lookup(id)) as extra_info FROM user_table; ``` 以上命令首先定义了一个名为 `async_lookup` 的新函数指向之前编写的 Java 类路径;接着在一个 SELECT 语句中演示了它的基本用法——对每一行输入都发起一次独立的异步查询并将得到的结果附加给原始字段列表[^2]。 需要注意的是,尽管这种方式能够很好地解决某些特定问题,但在设计过程中仍需谨慎考虑诸如错误处理机制、并发控制等因素的影响。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值