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() } }