Flink读写文件

1 读取文件-readFile

Q:什么是文件数据源?
A:Apache Flink提供了一个可重置的数据源连接器,支持将文件中的数据提取成数据流。
(该文件系统是flink的一部分因此无需同Kafka那样添加依赖包)

示例
val lineReader = new TextInputFormat(null)
streamLocal.readFile[String](
    lineReader,
    //    "hdfs://master:8020/file/a.txt",
    "file:///D:\\tmp\\a.txt",
    FileProcessingMode.PROCESS_CONTINUOUSLY,
    5000L)
    .map(x => {
      val value = x.split(",")
      (value(0), value(1).toInt)
})
  • TextInputFormat:会按(由换行符指定)行来读取文本文件。
  • PROCESS_CONTINUOUSLY:会以时间间隔周期性的扫描文件,如果目标文件发生了修改,就会将修改过的文件重新读取一份。(注意这里的读取是全部读取)
  • PROCESS_ONE:不会以时间间隔扫描文件。

2 写入到文件-StreamingFileSink

  应用配置了检查点,且读取的数据源可以提供在故障中恢复的功能,那么StreamFileSink就可以提供端到端的一次性保障。

  2.1 在了解-StreamingFileSink之前你需要了解的知识点

  • 进行,等待,和完成:

    • 当数据写入文件时,文件会进入“进行” (part-0-0.inprogress)状态。当RollingPolicy决定生成文件时,原文件会关闭,并通过重命名(part-0-0.wait)进入等待状态,在下一次检查点完成后处于等待状态的文件将(再次重命名part-0-0)进入完成状态。
  • RollingPolicy:

    • 使用行编码写入文件的时候,我们可以通过定义RollingPolicy来决定何时创建分块文件(什么是分块文件会在下面的行编码中有讲到),但在批量编码中是无法选择RollingPolicy的。批量编码只能与检查点结合起来使用,即当每次生成一个检查点的时候,就会对应的生成一个新的分块文件。

    2.1.1 结论

      如果应用没有开启检查点,StreamingFlinkSink将永远不会把等待状态的文件变成完成状态。

  2.2 行编码

示例
val input = streamLocal.socketTextStream("master", 9999)
val sink = StreamingFileSink.forRowFormat(
    	new Path("file:///D:\\tmp"),
    	new SimpleStringEncoder[String]("UTF-8")
    	).build()
   input.addSink(sink)
  • 自定义BucketAssigner

    • 默认:以程序写入的时间在tmp下生成每小时一个的目录
      (该目录的形成方式是flink通过调用BucketAssigner来完成,我们可以通过修改BucketAssigner的方式来自定义。)
      例如:
      • 数据是2019:08:04 10:10:01秒写入的,就会在D:\tmp下生成一个2019-08-04 - - 10的一个目录。
      • 数据是2019:08:04 11:10:01秒写入的,就会在D:\tmp下生成一个2019-08-04 - - 11的一个目录。
  • 目录下的分块文件

    • 每一个目录(019-08-04 - -11)中都会包含很多分块文件(part file),如下,该分块文件的形成方式是通过StreamingFileSink的多个并行实例来进行并发写入
      的:下面的分块文件代表的是:编号为0的的任务写出的第0个文件
  • 上面的分块文件是如何创建的:RollingPolicy?

    • RollingPolicy用来决定任务何时创建一个新的分块文件。默认是现有文件大小超过128M或打开时间超过60S就会创建一个新的分块文件。

    2.2.1 行编码自定义-BucketAssigner

       我们可以通过修改BucketAssigner的方式来自定义

示例
 val sink: StreamingFileSink[String] = StreamingFileSink.forRowFormat(
    new Path("file:///D:\\tmp"),
    new SimpleStringEncoder[String]("UTF-8")
  )
    .withBucketAssigner(new BucketAssigner[String, String] { //对应的是输入类型和BucketID(目录名称)类型
      def getPartitions(element: String): String = {
        val date_L = element.split(",")(1).toLong //将数据中的事件时间作为目录进行存储
        val file_path = new SimpleDateFormat("yyyyMMdd").format(new Date(date_L));
        s"dt=$file_path"
      }

      override def getBucketId(element: String, context: BucketAssigner.Context): String = {
        var partitionValue = ""
        try {
          partitionValue = getPartitions(element)
        } catch {
          case e: Exception => partitionValue = "00000000"
        }
        partitionValue
      }
      override def getSerializer: SimpleVersionedSerializer[String] = {
        SimpleVersionedStringSerializer.INSTANCE;
      }
    }).build()

  2.3 批量编码

  • 在前面的行编码中我们使用的写入方式是行写入。
  • 在行编码中每条记录都会被单独进行编码然后添加到分块文件中,然而在批量编码模式下,记录会被攒成批,然后一次性的写入。Apache Pqrquet会以列式的方式组织和压缩,因此该文件格式需要以批量编码的方式写入。
示例
/**
  * 需要导入的依赖包
  * "org.apache.flink" % "flink-parquet" % "1.7.0"
  * "org.apache.parquet" % "parquet-avro" % "1.10.1"
  * */
object 自定义批量写入文件  {
  case class Avro_Pojo(name: String, age: Int)
  def main(args: Array[String]): Unit = {
    import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
    val streamLocal = StreamExecutionEnvironment.createLocalEnvironment(1)
    //由于检查点为了支持容错会以固定间隔来进行创建,在这里我定义成周期为10秒
    streamLocal.enableCheckpointing(5000L)
    import org.apache.flink.streaming.api.scala._ //如果数据集是无限的可以引入这个包,来进行隐式转换操作
    val input = streamLocal.socketTextStream("master", 9999)
      .map(x=>{
      val value = x.split(",")
      val end = Avro_Pojo(value(0), value(1).toInt)
      end
    })

    val sink = StreamingFileSink.forBulkFormat(
      new Path("file:///D:\\tmp"),
      //妈的注意这里是:forReflectRecord不是forSpecificRecord,小心被坑
      ParquetAvroWriters.forReflectRecord(classOf[Avro_Pojo]))
      .build()
    input.addSink(sink).name("ceshi")

    streamLocal.execute("write file")
  }
}

    2.3.1 批量编码自定义-BucketAssigner

      批量编码自定义BucketAssigner,与上面的行编码自定义是一样的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章