Flink DataSink

同DataSource一样,flink流处理和批处理也都内置了很多DataSink,可以满足部分应用场景。但平时使用的应用场景比较多,光是靠内置的DataSink完全不满足日常使用。flink也考虑到了这个问题,允许我们实现自定义的DataSink。

1 批处理

最简单的DataSink就是print(),平时我们在编写flink程序时进行简单测试的时候通常都会使用print()在控制台上打印处理后的结果数据。
真正业务应用比较多的还是writeAsCsv(),writeAsText()。还可以通过继承write()中FileOutputFormat类来实现将结果数据输出到其他格式文件中。

    targetDataSet.print()
    targetDataSet.writeAsCsv("file:///xxx/Documents/batch_csv")
    targetDataSet.writeAsText("file:///xxx/Documents/batch_txt")
    targetDataSet.write(
      outputFormat: FileOutputFormat[T],
      filePath: String,
      writeMode: FileSystem.WriteMode = null)
01_batch_sink.png

2 流处理

2.1 kafka connector

kafka connector是flink提供给我们的自定义连接器,可以直接实例化FlinkKafkaProducer对象来将记录存放到kafka中。

object FlinkStreamDataSink {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    val original: DataStream[String] = ...

    // kafka sink properties参数
    val producerConf = new Properties()
    producerConf.setProperty("bootstrap.servers", "172.16.57.101:9092,172.16.57.102:9092,172.16.57.103:9092")
    producerConf.setProperty("zookeeper.connect", "172.16.57.101:2181,172.16.57.102:2181,172.16.57.103:2181")
    producerConf.setProperty("group.id", "leslie")

    // sink结果数据到kafka中
    original.addSink(new FlinkKafkaProducer010[String]("test_1", new SimpleStringSchema(), producerConf))

    env.execute("flink_data_sink")
  }
}
01_stream_sink_kafka.png

2.2 自定义DataSink

自定义DataSink和自定义DataSource一样简单,只需要继承SinkFunction接口并重写其中的invoke()方法。

object FlinkStreamCustomerDataSink {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment

    // kafka source properties参数
    val props = new Properties()
    props.setProperty("bootstrap.servers", "172.16.57.101:9092,172.16.57.102:9092,172.16.57.103:9092")
    props.setProperty("zookeeper.connect", "172.16.57.101:2181,172.16.57.102:2181,172.16.57.103:2181")
    props.setProperty("group.id", "leslie")
    props.setProperty("auto.offset.reset", "latest")

    val originalStream: DataStream[String] = env
      .addSource(new FlinkKafkaConsumer010[String](
        "test", // 被消费的kafka topic
        new SimpleStringSchema(), // 序列化
        props)) // kafka source properties参数

    val targetStream: DataStream[(String, String, Int)] = originalStream
      .flatMap(_.split(","))
      .map { name =>
        val sex = if (name.contains("e")) "男" else "女"
        val age = if (name.contains("e")) 23 else 18
        (name, sex, age) // 根据名字来构造人物的基本信息
      }

    // 自定义DataSink
    targetStream.addSink(new MysqlDataSink())

    env.execute("fink_consumer_data_sink")
  }
}

下文代码MysqlDataSink类继承RichSinkFunction类,实现将记录存放到mysql的目的。为了避免重复创建和销毁mysql连接,我们和自定义DataSouce一样继承"富函数",在open(),close()方法中实现连接的创建和销毁(前一篇博客Flink DataSouce中有提到open()方法仅在函数类实例化的时候调用一次,close()则是在实例对象销毁前调用一次)。

class MysqlDataSink extends RichSinkFunction[(String, String, Int)] {
  private var pStmt: PreparedStatement = _
  private var conn: Connection = _

  override def open(parameters: Configuration): Unit = {
    Class.forName("com.mysql.jdbc.Driver")
    val url = "jdbc:mysql://localhost:3306/test_for_mysql?useSSL=false"
    val username = "root"
    val password = "123456"
    conn = DriverManager.getConnection(url, username, password);
    val sql =
      """
        |insert into user (name, sex, age) values (?, ?, ?);
        |""".stripMargin
    pStmt = conn.prepareStatement(sql)
  }

  override def close(): Unit = {
    if (conn != null) conn.close()
    if (pStmt != null) pStmt.close()
  }

  // 主体方法,插入数据到mysql中
  override def invoke(value: (String, String, Int)): Unit = {
    pStmt.setString(1, value._1)
    pStmt.setNString(2, value._2)
    pStmt.setInt(3, value._3)
    pStmt.execute()
  }
}
02_stream_sink_customer.png

最后:
上边就是Flink DataSink的介绍部分。当然DataSink相关的知识并不只有这么一点点,此文只是些基础知识,各位老哥们以后遇到具体的场景具体处理。
下一篇博文将会把这两次提到的富函数给大家说说,也会通过富函数的讲解把flink的有状态编程也说说。

你可能感兴趣的:(Flink DataSink)