第二个在 Source 上面我们支持的功能就是 Watermark Alignment。
先说一下这个问题的背景,不管大家的作业当中有一个 Source 还是多个 Source ,经常会遇到的情况是不同 Source 之间读取的进度差异会很大,比方如果是 Kafka, Source 中的某一个 Partition 因为网络或者其他原因,它的进度远远落后于其他的 Partition。或者有两个 Source ,它们之间因为读取不同的外部系统导致产生不同的进度,这样就会导致一些下游的算子比如说我需要做一些 Join、我需要做 Aggregate,就需要等待所有并发的 Watermark 都前进到同一个位置之后才能够出发计算,只要有一个拖了后腿,那其他人的数据都要在状态里面等,这样就会导致后面某些需要用到的算子状态会越来越大,这实际上就是读取进度的不同所导致的。
针对这个问题,我们提出了这样一个 Watermark Alignment 的机制,在实现的时候,如果是同一个 Source 相对会简单一点,可以直接在这个 Source 的 Coordinator 、或者说是 Enumerator 里面就把这个事情做了。如果跨 Source 之间要实现这个能力,我们是在中间引入了一个叫 CoordinatorStore 的一个组件。它能够让不同的 Source 之间来交换一些信息,在这里面我们需要交换的就是 Watermark 信息, Source Operator 这边会周期性的给自己的 Coordinator 汇报当前处理的进度怎么样,然后 Source Coordinator 会周期性的检查当前进度的最小值,如果发现某些 Operator 读的太快了,有落在后面的并发或者说落在后面的 Source ,会让它先等一等,降低一下读取速度,等大家都追齐之后再往前读。这就是 Watermark Alignment 实现的一个细节。
二 、Sink API
介绍完 Source 之后,我们再为大家介绍一下 Sink API 。Sink API 也是经过了很多版本的迭代,最开始我们有 OutputFormat 和 SinkFunction,同样还要提醒大家这两个 API 在 2.0 里面是被废弃了的。在引入 Sink 之后我们也因为某些需求没法满足,所以推出了两个版本:Sink V1 、Sink V2 ,在这里主要介绍 Sink V2。Sink API 本身的设计相对来讲没有那么复杂,它不涉及到主从结构或者说不涉及到协调能力,Sink 本身只是一个工厂类,是来构建整个 Sink 的拓扑或者说各个组件的。其中最核心的组件就是 SinkWriter ,因为 Sink 本身需要往外写数据,所以不管是什么 Sink ,SinkWriter 一定是必不可少的,它的功能就是把上游的数据进行序列化,然后对应的写出到外部系统。如果说 Sink想要实现 Exactly-once 或者说第二阶段提交的能力,那在此基础上需要提供一个可选的 SinkCommitter 的组件。它们两个之间协调的方式就是在每个 Checkpoint 的时候,SinkWriter 会生成一个叫 Committable 的特殊的消息。
一般来讲数据库可能就是一个 Transaction,当 Checkpoint 触发的时候会产生这样一个 Committable,留给下面的 SinkCommitter,当所有的并发的 Checkpoint 都完成之后,我们会通过 SinkCommitter 将 Committable 提交到外部系统当中去,从而实现这样一个第二阶段提交的过程。有了这两个组件之后我们还是发现有一些需求很难满足,比如说像 Iceberg、Hive 这些 Sink ,它可能会涉及到 C