• Structured Streaming系列——输入与输出


    一、输入数据源

    1. 文件输入数据源(FIie)

    file数据源提供了很多种内置的格式,如csv、parquet、orc、json等等,就以csv为例:

     import spark.implicits._
        val userSchema = new StructType()
    .add("name", "string").add("age", "integer") val lines = spark.readStream .option("sep", ";") .schema(userSchema) .csv("file:///data/*") val query = lines.writeStream .outputMode("append") .format("console") .start() query.awaitTermination()

    在对应的目录下新建文件时,就可以在控制台看到对应的数据了。

    还有一些其他可以控制的参数:

    maxFilesPerTrigger  每个batch最多的文件数,默认是没有限制。比如我设置了这个值为1,那么同时增加了5个文件,这5个文件会每个文件作为一波数据,更新streaming dataframe。
    latestFirst  是否优先处理最新的文件,默认是false。如果设置为true,那么最近被更新的会优先处理。这种场景一般是在监听日志文件的时候使用。
    fileNameOnly  是否只监听固定名称的文件

    2.网络输入数据源(socket)

    一般都是基于这个socket来做测试。首先开启一个socket服务器(nc -lk 9999),然后streaming这边连接进行处理。

      spark.readStream
      .format("socket")
      .option("host", "localhost")
      .option("port", 9999)
      .load()

    3. 输入数据源(kafka)

    // Subscribe to 1 topic
    val df= spark                                                                                                                
    .readStream
    .format("kafka")
    .option("kafka.bootstrap.servers","host1:port1,host2:port2")
    .option("subscribe","topic1")
    .load()
    df.selectExpr("CAST(key AS STRING)","CAST(value AS STRING)")
    .as[(String,String)]
    
    // Subscribe to multiple topics
    val df= spark
    .readStream
    .format("kafka")
    .option("kafka.bootstrap.servers","host1:port1,host2:port2")
    .option("subscribe","topic1,topic2")
    .load()
    df.selectExpr("CAST(key AS STRING)","CAST(value AS STRING)")
    .as[(String,String)]
    
    // Subscribe to a pattern
    val df= spark
    .readStream
    .format("kafka")
    .option("kafka.bootstrap.servers","host1:port1,host2:port2")
    .option("subscribePattern","topic.*")
    .load()
    df.selectExpr("CAST(key AS STRING)","CAST(value AS STRING)")
    .as[(String,String)]

    以批的形式查询

    关于Kafka的offset,structured streaming默认提供了几种方式:

    //设置每个分区的起始和结束值
    val df = spark
      .read
      .format("kafka")
      .option("kafka.bootstrap.servers", "host1:port1,host2:port2")
      .option("subscribe", "topic1,topic2")
      .option("startingOffsets", """{"topic1":{"0":23,"1":-2},"topic2":{"0":-2}}""")
      .option("endingOffsets", """{"topic1":{"0":50,"1":-1},"topic2":{"0":-1}}""")
      .load()
    df.selectExpr("CAST(key AS STRING)", "CAST(value AS STRING)")
      .as[(String, String)]
    
    //配置起始和结束的offset值(默认)
    val df = spark
      .read
      .format("kafka")
      .option("kafka.bootstrap.servers", "host1:port1,host2:port2")
      .option("subscribePattern", "topic.*")
      .option("startingOffsets", "earliest")
      .option("endingOffsets", "latest")
      .load()
    df.selectExpr("CAST(key AS STRING)", "CAST(value AS STRING)")
      .as[(String, String)]

    Schema信息

    读取后的数据的Schema是固定的,包含的列如下:

    Column Type 说明
    key binary 信息key
    value binary 信息的value(我们自己的数据)
    topic string 主题
    partition int 分区
    offset long 偏移值
    timestamp long 时间戳
    timestampType int 类型

    source相关的配置

    无论是流的形式,还是批的形式,都需要一些必要的参数:

    • kafka.bootstrap.servers kafka的服务器配置,host:post形式,用逗号进行分割,如host1:9000,host2:9000
    • assign,以json的形式指定topic信息
    • subscribe,通过逗号分隔,指定topic信息
    • subscribePattern,通过java的正则指定多个topic
      assign、subscribe、subscribePattern同时之中能使用一个。

    其他比较重要的参数有:

      • startingOffsets, offset开始的值,如果是earliest,则从最早的数据开始读;如果是latest,则从最新的数据开始读。默认流是latest,批是earliest
      • endingOffsets,最大的offset,只在批处理的时候设置,如果是latest则为最新的数据
      • failOnDataLoss,在流处理时,当数据丢失时(比如topic被删除了,offset在指定的范围之外),查询是否报错,默认为true。这个功能可以当做是一种告警机制,如果对丢失数据不感兴趣,可以设置为false。在批处理时,这个值总是为true。
      • kafkaConsumer.pollTimeoutMs,excutor连接kafka的超时时间,默认是512ms
      • fetchOffset.numRetries,获取kafka的offset信息时,尝试的次数;默认是3次
      • fetchOffset.retryIntervalMs,尝试重新读取kafka offset信息时等待的时间,默认是10ms
      • maxOffsetsPerTrigger,trigger暂时不会用,不太明白什么意思。Rate limit on maximum number of offsets processed per trigger interval. The specified total number of offsets will be proportionally split across topicPartitions of different volume.

    二、输出数据源

    目前Structed Streaming有四种方式:

    1.File sink。写入到文件中。

    2.Foreach sink。对输出的记录进行任意计算。比如保存到mysql中。目前spark不支持直接写入外部数据库,只提供了Foreach接收器自己来实现,而且官网也没有示例代码。

    3.Console sink。输出到控制台,仅用于测试。

    4.Memory sink。以表的形式输出到内存,spark可以读取内存中的表,仅用于测试。

    5.Kafka sink。spark2.2.1更新了kafka sink,所以可以直接使用,如果你的版本低于2.2.1,那就只能使用第二个方法foreach sink来实现。

    在配置完输入,并针对DataFrame或者DataSet做了一些操作后,想要把结果保存起来。就可以使用DataSet.writeStream()方法,配置输出需要配置下面的内容:

    • format : 配置输出的格式
    • output mode:输出的格式
    • query name:查询的名称,类似tempview的名字
    • trigger interval:触发的间隔时间,如果前一个batch处理超时了,那么不会立即执行下一个batch,而是等下一个trigger时间在执行。
    • checkpoint location:为保证数据的可靠性,可以设置检查点保存输出的结果。

    1. output Mode

    只有三种类型

    • complete,把所有的DataFrame的内容输出,这种模式只能在做agg聚合操作的时候使用,比如ds.group.count,之后可以使用它
    • append,普通的dataframe在做完map或者filter之后可以使用。这种模式会把新的batch的数据输出出来,
    • update,把此次新增的数据输出,并更新整个dataframe。有点类似之前的streaming的state处理。

    2. 输出的类型

    2.1)file:保存成csv或者parquet

    DF.writeStream
      .format("parquet")
      .option("checkpointLocation", "path/to/checkpoint/dir")
      .option("path", "path/to/destination/dir")
      .start()

    2.2)console:直接输出到控制台。一般做测试的时候用这个比较方便(测试用)

    DF.writeStream
      .format("console")
      .start()

    2.3)memory:可以保存在内容,供后面的代码使用(测试用)

    DF.writeStream
      .queryName("aggregates")
      .outputMode("complete")
      .format("memory")
      .start()
    spark.sql("select * from aggregates").show()  

    2.4) kafka: 输出到kafka, 在spark 2.2.1以前用自定义实现写入。在spark2.2.1后提供了方法。

    spark 2.2.1之前写入kafka的方法

    自定义一个类KafkaSink继承ForeachWriter

    import java.util.Properties
     
    import org.apache.kafka.clients.producer.{KafkaProducer, ProducerRecord}
    import org.apache.spark.sql.{ForeachWriter, Row}
     
    class KafkaSink(topic: String, servers: String) extends ForeachWriter[Row]{
      val kafkaProperties = new Properties()
      kafkaProperties.put("bootstrap.servers", servers)
      kafkaProperties.put("key.serializer", "kafkashaded.org.apache.kafka.common.serialization.StringSerializer")
      kafkaProperties.put("value.serializer", "kafkashaded.org.apache.kafka.common.serialization.StringSerializer")
     
      val results = new scala.collection.mutable.HashMap
      var producer: KafkaProducer[String, String] = _
     
      override def open(partitionId: Long, version: Long): Boolean = {
        producer = new KafkaProducer(kafkaProperties)
        return true
      }
     
      override def process(value: Row): Unit = {
        val word = value.getAs[String]("word")
        val count = value.getAs[String]("count")
        producer.send(new ProducerRecord(topic, word, count))
      }
     
      override def close(errorOrNull: Throwable): Unit = {
        producer.close()
      }
    }

    spark 2.2.1以后写入kafka的方法

    // spark 2.2.1以后
    wordcount.writeStream .format(
    "kafka") .option("kafka.bootstrap.servers", "host1:port1,host2:port2") .option("topic", "wordcount") .start()

    2.5)foreach:参数是一个foreach的方法,用户可以实现这个方法实现写入mysql自定义的功能。

    import java.sql._
     
    import org.apache.spark.sql.{ForeachWriter, Row}
     
    class JDBCSink(url: String, userName: String, password: String) extends ForeachWriter[Row]{
     
      var statement: Statement = _
      var resultSet: ResultSet = _
      var connection: Connection = _
      // 初始化信息
      override def open(partitionId: Long, version: Long): Boolean = {
        
        Class.forName("com.mysql.jdbc.Driver")
        connection = DriverManager.getConnection(url, userName, password)
        statement = connection.createStatement()
        return true
      }
       // 执行操作
      override def process(value: Row): Unit = {
     
        val word= value.getAs[String]("word")
        val count = value.getAs[Integer]("count")
     
     
        val insertSql = "insert into webCount(word,count)" +
          "values('" + word + "'," + count + ")"
     
        statement.execute(insertSql)
      }
      // 结束操作
      override def close(errorOrNull: Throwable): Unit = {
          connection.close()
      }
    }
    import org.apache.spark.sql.SparkSession
    import org.apache.spark.sql.streaming.{ProcessingTime, Trigger}
     
    object KafkaStructedStreaming {
     
      def main(args: Array[String]): Unit = {
        val sparkSession = SparkSession.builder().master("local[2]").appName("streaming").getOrCreate()
     
        val df = sparkSession
            .readStream
            .format("socket")
            .option("host", "hadoop102")
            .option("port", "9999")
            .load()
     
        import sparkSession.implicits._
        val lines = df.selectExpr("CAST(value as STRING)").as[String]
        val weblog = lines.as[String].flatMap(_.split(" "))
     
        val wordCount = weblog.groupBy("value").count().toDF("word", "count")
     
        val url ="jdbc:mysql://hadoop102:3306/test"
        val username="root"
        val password="000000"
     
        val writer = new JDBCSink(url, username, password)
     
        val query = wordCount.writeStream
            .foreach(writer)
            .outputMode("update")
            .trigger(ProcessingTime("10 seconds"))
            .start()
        query.awaitTermination()
    }

    参考原文链接:https://blog.csdn.net/a790439710/article/details/103027602

  • 相关阅读:
    PDF解决方案(3)--PDF转SWF
    PDF解决方案(2)--文件转PDF
    PDF解决方案(1)--文件上传
    为JS字符类型添加trim方法
    Python:面向对象之反射
    Python:面向对象的三大特性
    Python:面向对象初识
    Python:二分查找
    Python:函数递归
    Python:内置函数
  • 原文地址:https://www.cnblogs.com/yyy-blog/p/12753924.html
Copyright © 2020-2023  润新知