Spark(Structured Streaming)

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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值