SPARK 结构化流使用

一、SPARK结构化流一般包含以下几个部分:

  • 获取一个或者多个流数据源:可以是kafka,文件, socket
  • 使用DataFrame或者DataSet形式转换和操作流的逻辑
  • 定义输出模式和触发器
  • 写入下游存储系统中

二、数据源

  • socket
    object SocketStream {
      def main(args: Array[String]): Unit = {
    
        val spark: SparkSession = SparkSession.builder()
          .appName("socket")
          .master("local[*]")
          .getOrCreate()
        spark.sparkContext.setLogLevel("WARN")
        import spark.implicits._
    
        val streamLines: DataFrame = spark.readStream.format("socket")
          .option("host", "node")
          .option("port", 9999)
          .load()
        val result: DataFrame = streamLines.as[String].flatMap(_.split(","))
          .groupBy("value")
          .count()
        val query: StreamingQuery = result.writeStream.format("console")
          .outputMode("complete")
          .start()
        query.awaitTermination()
    
        spark.close()
      }
    }

     

  • kafka
    // https://spark.apache.org/docs/latest/structured-streaming-kafka-integration.html
    val stream: DataFrame = spark.readStream.format("kafka") .option("kafka.bootstrap.servers", "node:9092") .option("subscribe", "stream") .option("includeHeaders", "true") .option("kafka.group.id", "spark") .option("startingOffsets", "earliest") .load()

     

三、输出模式

  • append模式:默认的输出模式,只有追加到结果表的新行才会被发送到指定的输出接收器。只有自上次触发后在结果表中附加的新行将被写入外部存储器,仅适用于结果表中现有行不会更改的查询;
  • complete模式:将数据完全从内存写入下游存储系统,当对流数据进行聚合时,使用此模式;
  • update模式:与complete不同在于那些没有改变的行,将不会被写出来,此模式不输出未更改的行

 

四、触发器类型

  • 默认:spark使用微批模型,当前一批数据处理完成之后,立即处理下一批数据
  • 固定周期:根据用于提供的周期处理数据,当上一周期由于某种原因导致处理超时那么前一批处理完后,立即处理下一批数据,不会等到下一个周期
  • 一次性:只处理一次,一旦处理完后,spark将立即停止流程序,一般构建一个集群,并且每天处理几次数据更划算
  • 持续:用于低延迟需求,调用持续处理模型 
  • example
    // 1、固定周期处理,5s
     tumRes.writeStream.format("console")
          .trigger(Trigger.ProcessingTime("5 seconds"))
          .start()
    
     // 2、只处理一次
     tumRes.writeStream.format("console")
          .trigger(Trigger.Once())
          .start()
    
     // 3、持续处理
     tumRes.writeStream.format("console")
          .trigger(Trigger.Continuous("2 seconds"))
          .start()

     

五、数据接收器

  一个数据接收器必须支持幂等处理

  • File:仅支持Append的输出模式
  • Foreach:支持Append,Complete, Update三种模式
  • Console
  • Kafka

 

六、测试

package com.shydow.stream

import java.lang
import java.sql.Timestamp

import com.alibaba.fastjson.{JSON, JSONObject}
import org.apache.spark.sql.streaming.{OutputMode, StreamingQuery, Trigger}
import org.apache.spark.sql.{DataFrame, Dataset, SparkSession}

/**
 * @author Rainbow
 * @date 2022/2/13 14:05
 */


object KafkaStream {

  case class ClickLog(channelID: Long, userID: Long, country: String, browserType: String, timeStamp: Timestamp)

  def main(args: Array[String]): Unit = {

    val spark: SparkSession = SparkSession.builder()
      .appName("app")
      .master("local[*]")
      .getOrCreate()
    spark.sparkContext.setLogLevel("WARN")
    import spark.implicits._
    import org.apache.spark.sql.functions._

    val stream: DataFrame = spark.readStream.format("kafka")
      .option("kafka.bootstrap.servers", "node:9092")
      .option("subscribe", "stream")
      .option("includeHeaders", "true")
      .option("kafka.group.id", "spark")
      .option("startingOffsets", "earliest")
      .load()

    val source: Dataset[(String, String, Array[(String, Array[Byte])])] = stream.selectExpr("CAST(key AS STRING)", "CAST(value AS STRING)", "headers")
      .as[(String, String, Array[(String, Array[Byte])])]

    val res: Dataset[ClickLog] = source.map { t =>
      val value: String = t._2
      val nObject: JSONObject = JSON.parseObject(value)
      val timestamp: lang.Long = nObject.getLong("timeStamp")
      val event_time = new Timestamp(timestamp)
      val jSONObject: JSONObject = nObject.getJSONObject("message")
      val channelID: lang.Long = jSONObject.getLong("channelID")
      val userID: lang.Long = jSONObject.getLong("userID")
      val country: String = jSONObject.getString("country")
      val browserType: String = jSONObject.getString("browserType")
      ClickLog(channelID, userID, country, browserType, event_time)
    }

    // 计算浏览器的pv,uv
    res.createOrReplaceTempView("click")
    val frame: DataFrame = spark.sql(
      """
        |SELECT browserType, count(1) as pv, approx_count_distinct(userID) as uv
        |FROM click
        |GROUP BY browserType
        |""".stripMargin)

    // window ope and event time
    val tumRes: DataFrame = res.groupBy(
      window($"timeStamp", "60 seconds", "10 seconds"),
      $"browserType"
    ).count()

    // 触发器的使用
    // 1、固定周期处理,5s
    tumRes.writeStream.format("console")
      .trigger(Trigger.ProcessingTime("5 seconds"))
      .start()

    // 2、只处理一次
    tumRes.writeStream.format("console")
      .trigger(Trigger.Once())
      .start()

    // 3、持续处理
    tumRes.writeStream.format("console")
      .trigger(Trigger.Continuous("2 seconds"))
      .start()


    val query: StreamingQuery = tumRes.writeStream.format("console")
      .outputMode(OutputMode.Update())
      .start()
    query.awaitTermination()

    spark.close()
  }
}

 

 

posted @ 2022-02-13 19:34  Shydow  阅读(255)  评论(0编辑  收藏  举报