StreamingFileSink

       连接器提供了一个 Sink 来将分区文件写入到支持 Flink FileSystem 接口的文件系统中。

    Streaming File Sink 会将数据写入到桶中。由于输入流可能是无界的,每个桶中的数据被划分为多个有限大小的文件。如何分桶是可以配置的,默认使用基于时间的分桶策略,这种策略每个小时创建一个新的桶,桶中包含的文件将记录所有该小时内从流中接收到的数据。

  桶目录中的实际输出数据会被划分为多个部分文件(part file),每一个接收桶数据的 Sink Subtask ,至少包含一个部分文件(part file)。额外的部分文件(part file)将根据滚动策略创建,滚动策略是可以配置的。默认的策略是根据文件大小和超时时间来滚动文件。超时时间指打开文件的最长持续时间以及文件关闭前的最长非活动时间。

  使用 StreamingFileSink 时需要启用 Checkpoint ,每次做 Checkpoint 时写入完成。如果 Checkpoint 被禁用,部分文件(part file)将永远处于 'in-progress' 或 'pending' 状态,下游系统无法安全地读取。

  

 

   在下游系统中使用 StreamingFileSink 的输出,我们需要了解输出文件的命名规则和生命周期。由上图可知,部分文件(part file)可以处于以下三种状态之一:

1).In-progress :

  当前文件正在写入中

2).Pending :

  当处于 In-progress 状态的文件关闭(closed)了,就变为 Pending 状态

3).Finished :

  在成功的 Checkpoint 后,Pending 状态将变为 Finished 状态,处于 Finished 状态的文件不会再被修改,可以被下游系统安全地读取。
  部分文件索引在每个 subtask 内部是严格递增的(按文件创建顺序)。但是索引并不总是连续的。当 Job 重启后,所有部分文件的索引从 `max part index + 1` 开始, 这里的 `max part index` 是所有 subtask 中索引的最大值。

文件编码格式

  StreamingFileSink 支持行编码格式和批量编码格式,比如 Apache Parquet 。这两种变体可以使用以下静态方法创建:

  1).Row-encoded sink:

  StreamingFileSink.forRowFormat(basePath, rowEncoder)

  2).Bulk-encoded sink:

  StreamingFileSink.forBulkFormat(basePath, bulkWriterFactory)

//行编码
final StreamingFileSink<String> sink = StreamingFileSink
    .forRowFormat(new Path(outputPath), new SimpleStringEncoder<String>("UTF-8"))
    .withRollingPolicy(
        DefaultRollingPolicy.builder()
            .withRolloverInterval(TimeUnit.MINUTES.toMillis(15))
            .withInactivityInterval(TimeUnit.MINUTES.toMillis(5))
            .withMaxPartSize(1024 * 1024 * 1024)
            .build())
  .build();
  //批量编码
 final StreamingFileSink<GenericRecord> sink = StreamingFileSink
  .forBulkFormat(outputBasePath, ParquetAvroWriters.forGenericRecord(schema))
  .build();

  创建行或批量编码的 Sink 时,我们需要指定存储桶的基本路径和数据的编码逻辑。批量编码模式仅支持 OnCheckpointRollingPolicy 策略, 在每次 checkpoint 的时候切割文件。

桶分配

  桶分配逻辑定义如何将数据结构化为基本输出目录中的子目录,行格式和批量格式都使用 DateTimeBucketAssigner 作为默认的分配器。默认情况下,DateTimeBucketAssigner 基于系统默认时区每小时创建一个桶,格式如下:yyyy-MM-dd--HH 。日期格式(即桶的大小)和时区都可以手动配置。

  Flink 有两个内置的 BucketAssigners :

  1).DateTimeBucketAssigner :默认基于时间的分配器

  2).BasePathBucketAssigner :将所有部分文件(part file)存储在基本路径中的分配器(单个全局桶)

滚动策略

  滚动策略 RollingPolicy 定义指定的文件在何时关闭(closed)并将其变为 Pending 状态,随后变为 Finished 状态。处于 Pending 状态的文件会在下一次 Checkpoint 时变为 Finished 状态,通过设置 Checkpoint 间隔时间,可以控制部分文件(part file)对下游读取者可用的速度、大小和数量。

  Flink 有两个内置的滚动策略:

  1).DefaultRollingPolicy

  2).OnCheckpointRollingPolicy

part file相关配置项

  已经完成的文件和进行中的文件仅能通过文件名格式进行区分。默认情况下,文件命名格式如下所示:

  1).In-progress/Pending:

  part-<subtaskIndex>-<partFileIndex>.inprogress.uid

  2).FINISHED:

  part-<subtaskIndex>-<partFileIndex>

  Flink 允许用户通过 OutputFileConfig 指定部分文件名的前缀和后缀。

 
OutputFileConfig config = OutputFileConfig
 .builder()
 .withPartPrefix("prefix")
 .withPartSuffix(".ext")
 .build();
StreamingFileSink<Tuple2<Integer, Integer>> sink = StreamingFileSink
 .forRowFormat((new Path(outputPath), new SimpleStringEncoder<>("UTF-8"))
 .withBucketAssigner(new KeyBucketAssigner())
 .withRollingPolicy(OnCheckpointRollingPolicy.build())
 .withOutputFileConfig(config)
 .build();

示例

public class StreamingFileSinkTest {

    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        DataStreamSource<User> stream = env.fromElements(new User("1001", "小明"),
                new User("1002", "小王"),
                new User("1003", "小李"), new User("1004", "小美"),
                new User("1005", "小刚"),
                new User("1006", "笑笑"));

        DefaultRollingPolicy<User, String> rollPolicy = DefaultRollingPolicy.builder()
                .withRolloverInterval(TimeUnit.MINUTES.toMillis(2))
                .withInactivityInterval(TimeUnit.MINUTES.toMillis(5))
                .withMaxPartSize(128 * 1024 * 1024).build();

        OutputFileConfig config = OutputFileConfig.builder().withPartPrefix("prefix").withPartSuffix(".txt").build();
        StreamingFileSink<User> fileSink = StreamingFileSink
                .forRowFormat(new Path("./output"), new SimpleStringEncoder<User>("UTF-8"))
                /**采用默认的分桶策略DateTimeBucketAssigner,它基于时间的分配器,每小时产生一个桶,格式如下yyyy-MM-dd--HH*/
                .withBucketAssigner(new DateTimeBucketAssigner<>())
                .withRollingPolicy(rollPolicy)
                /*桶检查间隔,这里设置为1s*/
                .withBucketCheckInterval(1)
                .withOutputFileConfig(config).build();

        stream.addSink(fileSink);

        env.execute("streaming file test");
    }
}

 

  

  

posted on 2022-05-22 17:37  溪水静幽  阅读(812)  评论(0)    收藏  举报