1. 结构化流介绍
Structured Streaming 是基于Spark SQL引擎的可扩展和和可容错的流处理引擎。这样我们可以像处理静态批计算一样处理流,随着流数据的追加,Spark SQL将不断更新最终计算结果。同事,我们可以使用Scala、Java、Python或者R语言的Dataset/DataFrame API去处理流聚合(streaming aggregations)、事件时间窗口(event-time windows)、流和批的joins(stream-to-batch joins)等,以同样的优化Spark SQL引擎执行计算。系统通过检查点(checkpointing)、预写日志(Write-Ahead Logs)的方式确保端到端的精确一致性。
Structured Streaming queries本质上还是一个微批处理引擎能够达到端到端的100微秒的延迟和精确一致性,但从Spark 2.3版本开始使用了一个新的低延迟处理模型——Continuous Processing,能够达到1微秒的延迟同时达到at-least-once guarantees。在使用时,不需要在查询中改变对Dataset/DATa Frame的操作算子,就能使用基于该模型的引用。
2. Quick Example
运用Structured Streaming API 对来自网络端口的数据进行处理示例。
import org.apache.log4j.{Level, Logger}
import org.apache.spark.SparkConf
import org.apache.spark.sql.streaming.StreamingQuery
import org.apache.spark.sql.{DataFrame, Dataset, ForeachWriter, Row, SparkSession}
object StructuredStreaming {
def main(args: Array[String]): Unit = {
// 设置只打印error的日志
Logger.getLogger("org").setLevel(Level.ERROR)
// 创建配置对象
val conf: SparkConf = new SparkConf().setAppName("aa").setMaster("local[*]")
// 创建应用入口对象
val spark: SparkSession = SparkSession
.builder
.config(conf)
.getOrCreate()
import spark.implicits._
// 连接端口 读取的时一行行的字符串 默认 字段名:类型=value:String
val lines: DataFrame = spark.readStream
.format("socket")
.option("host", "hadoop")
.option("port", 9999)
.load()
lines.printSchema() // value: string (nullable = true)
// 将DataFrame转换成Dataset并运行转化算子进行操作
val words: Dataset[String] = lines.as[String].flatMap(_.split(" "))
words.printSchema() //value: string (nullable = true)
// word count
val wordCounts: DataFrame = words.groupBy("value").count()
val query: StreamingQuery = wordCounts.writeStream
.outputMode("complete")
.format("console")
.start()
query.awaitTermination()
}
} // nc -lk port
对于Spark而言,由readStream读取的流文本数据形成了一个无界的表,这个表是由一个字段名为value、数据类型为字符串(String)的字符串。我们想使用转换算子flatMap算子进行操作,所以就把它转换成了String类型的Dataset。最后启动该流计算需要使用start()方法。
最终结果:
-------------------------------------------
Batch: 0
-------------------------------------------
+-----+-----+
|value|count|
+-----+-----+
| b| 2|
| a| 3|
+-----+-----+
-------------------------------------------
Batch: 1
-------------------------------------------
+-----+-----+
|value|count|
+-----+-----+
| b| 3|
| a| 5|
+-----+-----+
3. 程序设计模型(Programming Model)
在结构化流中,关键点是将流数据处理成一个不断追加数据的表。所以其和原有的批处理模型很相似。你可以对待你的流计算像一个查询静态表的批计算。
3.1 基本概念
每当到达一条新的数据就像在表中增加了一行数据。
一个query会产生一个“Result table”。每一个触发间隔(例如:每1秒)新的数据行会被追加在输入表中,然后更新结果表。
输出模式可以定义成不同的模式:
Complete Mode:每次都会全量输出流经的数据;
Append Mode:只有新的数据会被输出,已经输出的数据不会再次输出,适用于输出过的结果不会改变的场景;
Update Mode:仅已经输出过的结果发生变化或新出现的结果;
下面是Quick Example的流程图:
结构化流并没由保存整个表的数据,只是把新到的数更新到结果中,中间保存了很少的状态数据。在这个过程中,开发人员不用考虑聚合、容错、数据一致性等。
3.2 Handling Event-time and Late Data
Spark 中也有Event-time和Late Data概念
3.3 创建流DataFrames和流Datasets
流DataFrames可以通过DataStreamReader接口创建,SparkSession.readStream(),可以指定数据源的详细信息——数据格式、schema、可选项等。
3.3.1 Input Sources
Spark 提供了一些数据源格式。
File source
直接读取目录中的文件作为流数据。文件以其修改的时间作为时间顺序被处理。也可以自定义文件顺序。支持CVS,JSON,ORC,Parquet文件格式。
Kafka source
可以从Kfka中读取数据,和0.10.0及以上的版本兼容,可以看Kafka Integration Guide获取更详细信息。
Socket source(测试用)
Rate source(测试用)
import org.apache.log4j.{Level, Logger}
import org.apache.spark.SparkConf
import org.apache.spark.sql.streaming.StreamingQuery
import org.apache.spark.sql.types.StructType
import org.apache.spark.sql.{DataFrame, Dataset, SparkSession}
object StructuredStreaming02 {
def main(args: Array[String]): Unit = {
// 设置只打印error的日志
Logger.getLogger("org").setLevel(Level.ERROR)
// 创建配置对象
val conf: SparkConf = new SparkConf().setAppName("aa").setMaster("local[*]")
// 创建应用入口对象
val spark: SparkSession = SparkSession
.builder
.config(conf)
.getOrCreate()
import spark.implicits._
//
val userSchema: StructType = new StructType().add("name", "string").add("age", "integer")
val csvDF: DataFrame = spark.readStream
.option("sep", ";") // 数据的分割符
.schema(userSchema) // 读取的数据设置字段名称和字段类型
.csv("F:\\spark181205\\src\\main\\resources")
csvDF.printSchema()
val groupDF: DataFrame = csvDF.groupBy("name").count()
val query: StreamingQuery = groupDF.writeStream
//Complete output mode not supported when there are no streaming aggregations on streaming DataFrames/Datasets;
.outputMode("complete") // append update模式可以不是聚合流
.format("console")
.start()
query.awaitTermination()
}
}
上面面所创建的流DataFrames是无类型的,意味着在编译的时候DataFrame的schema是没有被检查的,仅当查询被提交的运行时才检查类型。像map,flatMap等算子在编译的时候需要知道类型,因此可以把无类型的流DataFrames转换成有类型的流Datasets。
从Spark 3.1,可以通过DataStreamReader.table()创建流DataFrames。