Structured Streaming
-
全天目标
-
-
回顾和展望
-
入门案例
-
Stuctured Streaming
的体系和结构
-
1. 回顾和展望
-
本章目标
-
Structured Streaming
是Spark Streaming
的进化版, 如果了解了Spark
的各方面的进化过程, 有助于理解Structured Streaming
的使命和作用
本章过程
-
-
Spark
的API
进化过程 -
Spark
的序列化进化过程 -
Spark Streaming
和Structured Streaming
-
1.1. Spark 编程模型的进化过程
-
目标
-
Spark
的进化过程中, 一个非常重要的组成部分就是编程模型的进化, 通过编程模型可以看得出来内在的问题和解决方案
过程
-
-
编程模型
RDD
的优点和缺陷 -
编程模型
DataFrame
的优点和缺陷 -
编程模型
Dataset
的优点和缺陷
-
编程模型 | 解释 |
---|---|
|
|
|
|
|
|
-
-
面向对象的操作方式
-
可以处理任何类型的数据
-
-
-
运行速度比较慢, 执行过程没有优化
-
API
比较僵硬, 对结构化数据的访问和操作没有优化
-
-
-
针对结构化数据高度优化, 可以通过列名访问和转换数据
-
增加
Catalyst
优化器, 执行过程是优化的, 避免了因为开发者的原因影响效率
-
-
-
只能操作结构化数据
-
只有无类型的
API
, 也就是只能针对列和SQL
操作数据,API
依然僵硬
-
-
-
结合了
RDD
和DataFrame
的API
, 既可以操作结构化数据, 也可以操作非结构化数据 -
既有有类型的
API
也有无类型的API
, 灵活选择
-
RDD
的优点
RDD
的缺点
DataFrame
的优点
DataFrame
的缺点
Dataset
的优点
1.2. Spark 的 序列化 的进化过程
-
目标
-
Spark
中的序列化过程决定了数据如何存储, 是性能优化一个非常重要的着眼点,Spark
的进化并不只是针对编程模型提供的API
, 在大数据处理中, 也必须要考虑性能
过程
-
-
序列化和反序列化是什么
-
Spark
中什么地方用到序列化和反序列化 -
RDD
的序列化和反序列化如何实现 -
Dataset
的序列化和反序列化如何实现
-
-
Step 1: 什么是序列化和序列化
public class NonSerializable {
}
public static void main(String[] args) throws IOException {
// 序列化
JavaSerializable serializable = new JavaSerializable();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("/tmp/obj.ser"));
// 这里会抛出一个 “java.io.NotSerializableException: cn.itcast.NonSerializable” 异常
objectOutputStream.writeObject(serializable);
objectOutputStream.flush();
objectOutputStream.close();
// 反序列化
FileInputStream fileInputStream = new FileInputStream("/tmp/obj.ser");
ObjectInputStream objectOutputStream = new ObjectInputStream(fileInputStream);
JavaSerializable serializable1 = objectOutputStream.readObject();
}
-
序列化是什么
-
-
序列化的作用就是可以将对象的内容变成二进制, 存入文件中保存
-
反序列化指的是将保存下来的二进制对象数据恢复成对象
序列化对对象的要求
-
-
-
对象必须实现
Serializable
接口 -
对象中的所有属性必须都要可以被序列化, 如果出现无法被序列化的属性, 则序列化失败
限制
-
-
-
对象被序列化后, 生成的二进制文件中, 包含了很多环境信息, 如对象头, 对象中的属性字段等, 所以内容相对较大
-
因为数据量大, 所以序列化和反序列化的过程比较慢
序列化的应用场景
-
-
-
持久化对象数据
-
网络中不能传输
Java
对象, 只能将其序列化后传输二进制数据
-
Spark
中的序列化和反序列化的应用场景
rdd1.cache
rdd1.collect
-
RDD
中处理的是对象, 例如说字符串,Person
对象等 -
如果缓存
RDD
中的数据, 就需要缓存这些对象 -
对象是不能存在文件中的, 必须要将对象序列化后, 将二进制数据存入文件
-
广播变量
-
广播变量会分发到不同的机器上, 这个过程中需要使用网络, 对象在网络中传输就必须先被序列化
-
-
Shuffle
过程-
Shuffle
过程是由Reducer
从Mapper
中拉取数据, 这里面涉及到两个需要序列化对象的原因-
RDD
中的数据对象需要在Mapper
端落盘缓存, 等待拉取 -
Mapper
和Reducer
要传输数据对象
-
-
-
Spark Streaming
的Receiver
-
Spark Streaming
中获取数据的组件叫做Receiver
, 获取到的数据也是对象形式, 在获取到以后需要落盘暂存, 就需要对数据对象进行序列化
-
-
算子引用外部对象
class Unserializable(i: Int)
rdd.map(i => new Unserializable(i))
.collect
.foreach(println)-
在
Map
算子的函数中, 传入了一个Unserializable
的对象 -
Map
算子的函数是会在整个集群中运行的, 那Unserializable
对象就需要跟随Map
算子的函数被传输到不同的节点上 -
如果
Unserializable
不能被序列化, 则会报错
RDD
的序列化conf.set(“spark.serializer”, “org.apache.spark.serializer.KryoSerializer”)
conf.registerKryoClasses(Array(classOf[Person]))val sc = new SparkContext(conf)
rdd.map(arr => Person(arr(0), arr(1), arr(2)))
Step 4:DataFrame
和Dataset
中的序列化总结-
当需要将对象缓存下来的时候, 或者在网络中传输的时候, 要把对象转成二进制, 在使用的时候再将二进制转为对象, 这个过程叫做序列化和反序列化
-
在
Spark
中有很多场景需要存储对象, 或者在网络中传输对象-
Task
分发的时候, 需要将任务序列化, 分发到不同的Executor
中执行 -
缓存
RDD
的时候, 需要保存RDD
中的数据 -
广播变量的时候, 需要将变量序列化, 在集群中广播
-
RDD
的Shuffle
过程中Map
和Reducer
之间需要交换数据 -
算子中如果引入了外部的变量, 这个外部的变量也需要被序列化
-
-
RDD
因为不保留数据的元信息, 所以必须要序列化整个对象, 常见的方式是Java
的序列化器, 和Kyro
序列化器 -
Dataset
和DataFrame
中保留数据的元信息, 所以可以不再使用Java
的序列化器和Kyro
序列化器, 使用Spark
特有的序列化协议, 生成UnsafeInternalRow
用以保存数据, 这样不仅能减少数据量, 也能减少序列化和反序列化的开销, 其速度大概能达到RDD
的序列化的20
倍左右
1.3. Spark Streaming 和 Structured Streaming
目标和过程-
目标
-
理解
Spark Streaming
和Structured Streaming
之间的区别, 是非常必要的, 从这点上可以理解Structured Streaming
的过去和产生契机
过程
-
-
Spark Streaming
时代 -
Structured Streaming
时代 -
Spark Streaming
和Structured Streaming
-
Spark Streaming
时代Structured Streaming
时代Spark Streaming
和Structured Streaming
2. Structured Streaming 入门案例
-
目标
-
了解
Structured Streaming
的编程模型, 为理解Structured Streaming
时候是什么, 以及核心体系原理打下基础
步骤
-
-
需求梳理
-
Structured Streaming
代码实现 -
运行
-
验证结果
-
2.1. 需求梳理
目标和过程-
目标
-
理解接下来要做的案例, 有的放矢
步骤
-
-
需求
-
整体结构
-
开发方式
-
-
需求
- 整体结构
- 开发方式和步骤
总结-
简单来说, 就是要进行流式的词频统计, 使用
Structured Streaming
2.2. 代码实现
目标和过程-
目标
-
实现
Structured Streaming
部分的代码编写
步骤
-
-
创建文件
-
创建
SparkSession
-
读取
Socket
数据生成DataFrame
-
将
DataFrame
转为Dataset
, 使用有类型的API
处理词频统计 -
生成结果集, 并写入控制台
-
object SocketProcessor {
def main(args: Array[String]): Unit = {
// 1. 创建 SparkSession val spark = SparkSession.builder() .master("local[6]") .appName("socket_processor") .getOrCreate() spark.sparkContext.setLogLevel("ERROR") <i class="conum" data-value="1"></i><b>(1)</b> import spark.implicits._ // 2. 读取外部数据源, 并转为 Dataset[String] val source = spark.readStream .format("socket") .option("host", "127.0.0.1") .option("port", 9999) .load() .as[String] <i class="conum" data-value="2"></i><b>(2)</b> // 3. 统计词频 val words = source.flatMap(_.split(" ")) .map((_, 1)) .groupByKey(_._1) .count() // 4. 输出结果 words.writeStream .outputMode(OutputMode.Complete()) <i class="conum" data-value="3"></i><b>(3)</b> .format("console") <i class="conum" data-value="4"></i><b>(4)</b> .start() <i class="conum" data-value="5"></i><b>(5)</b> .awaitTermination() <i class="conum" data-value="6"></i><b>(6)</b>
}
}1 调整 Log
级别, 避免过多的Log
影响视线2 默认 readStream
会返回DataFrame
, 但是词频统计更适合使用Dataset
的有类型API
3 统计全局结果, 而不是一个批次 4 将结果输出到控制台 5 开始运行流式应用 6 阻塞主线程, 在子线程中不断获取数据 总结-
Structured Streaming
中的编程步骤依然是先读, 后处理, 最后落地 -
Structured Streaming
中的编程模型依然是DataFrame
和Dataset
-
Structured Streaming
中依然是有外部数据源读写框架的, 叫做readStream
和writeStream
-
Structured Streaming
和SparkSQL
几乎没有区别, 唯一的区别是,readStream
读出来的是流,writeStream
是将流输出, 而SparkSQL
中的批处理使用read
和write
2.3. 运行和结果验证
目标和过程-
目标
-
代码已经编写完毕, 需要运行, 并查看结果集, 因为从结果集的样式中可以看到
Structured Streaming
的一些原理
步骤
-
-
开启
Socket server
-
运行程序
-
查看数据集
-
-
开启
- 查看结果集
Socket server
和运行程序总结-
运行的时候需要先开启
Socket server
-
Structured Streaming
的 API 和运行也是针对结构化数据进行优化过的
3. Stuctured Streaming 的体系和结构
-
目标
-
了解
Structured Streaming
的体系结构和核心原理, 有两点好处, 一是需要了解原理才好进行性能调优, 二是了解原理后, 才能理解代码执行流程, 从而更好的记忆, 也做到知其然更知其所以然
步骤
-
-
WordCount
的执行原理 -
Structured Streaming
的体系结构
-
3.1. 无限扩展的表格
目标和过程-
目标
-
Structured Streaming
是一个复杂的体系, 由很多组件组成, 这些组件之间也会进行交互, 如果无法站在整体视角去观察这些组件之间的关系, 也无法理解Structured Streaming
的全局
步骤
-
-
了解
Dataset
这个计算模型和流式计算的关系 -
如何使用
Dataset
处理流式数据? -
WordCount
案例的执行过程和原理
-
- 如何使用
Dataset
和流式计算Dataset
这个编程模型表示流式计算?WordCount
的原理总结-
Dataset
不仅可以表达流式数据的处理, 也可以表达批量数据的处理 -
Dataset
之所以可以表达流式数据的处理, 因为Dataset
可以模拟一张无限扩展的表, 外部的数据会不断的流入到其中
3.2. 体系结构
目标和过程-
目标
-
Structured Streaming
是一个复杂的体系, 由很多组件组成, 这些组件之间也会进行交互, 如果无法站在整体视角去观察这些组件之间的关系, 也无法理解Structured Streaming
的核心原理
步骤
-
-
体系结构
-
StreamExecution
的执行顺序
-
-
体系结构
- 增量查询
StreamExecution
的执行顺序总结-
StreamExecution
是整个Structured Streaming
的核心, 负责在流上的查询 -
StreamExecution
中三个重要的组成部分, 分别是Source
负责读取每个批量的数据,Sink
负责将结果写入外部数据源,Logical Plan
负责针对每个小批量生成执行计划 -
StreamExecution
中使用StateStore
来进行状态的维护
4. Source
-
目标和过程
4.1. 从 HDFS 中读取数据
-
目标和过程
4.1.1. 案例结构
-
目标和步骤
- 案例流程
- 实现步骤
- 难点和易错点
- 总结
4.1.2. 产生小文件并推送到 HDFS
-
目标和步骤
- 代码编写
for index in range(100):
content = “”"
{“name”:“Michael”}
{“name”:“Andy”, “age”:30}
{“name”:“Justin”, “age”:19}
“”"file_name = <span class="hljs-string">"/export/dataset/text{0}.json"</span>.format(index) <span class="hljs-keyword">with</span> open(file_name, <span class="hljs-string">"w"</span>) <span class="hljs-keyword">as</span> file: <i class="conum" data-value="1"></i><b>(<span class="hljs-number">1</span>)</b> file.write(content) os.system(<span class="hljs-string">"/export/servers/hadoop/bin/hdfs dfs -mkdir -p /dataset/dataset/"</span>) os.system(<span class="hljs-string">"/export/servers/hadoop/bin/hdfs dfs -put {0} /dataset/dataset/"</span>.format(file_name))</code></pre>
1 创建文件, 使用这样的写法是因为 with
是一种Python
的特殊语法, 如果使用with
去创建文件的话, 使用结束后会自动关闭流4.1.3. 流式计算统计 HDFS 上的小文件
-
目标和步骤
- 代码
spark.sparkContext.setLogLevel(“WARN”)
val userSchema = new StructType()
.add(“name”, “string”)
.add(“age”, “integer”)val source = spark
.readStream
.schema(userSchema)
.json(“hdfs://node01:8020/dataset/dataset”)val result = source.distinct()
result.writeStream
总结
.outputMode(OutputMode.Update())
.format(“console”)
.start()
.awaitTermination()4.1.4. 运行和流程总结
-
目标和步骤
- 运行 Python 程序
- 运行 Spark 程序
运行流程序
spark-submit --class cn.itcast.structured.HDFSSource ./original-streaming-0.0.1.jar
总结4.2. 从 Kafka 中读取数据
-
目标和步骤
4.2.1 Kafka 的场景和结构
-
目标和步骤
- Kafka 是一个 Pub / Sub 系统
- Kafka 的特点
- Topic 和 Partitions
- 总结
4.2.2. Kafka 和 Structured Streaming 整合的结构
-
目标和步骤
- Topic 的 Offset
- Kafka 和 Structured Streaming 整合的结构
- Structured Streaming 读取 Kafka 消息的三种方式
- 总结
4.2.3. 需求介绍
-
目标和步骤
- 需求
- 数据转换
val camerasType = new StructType()
.add(“device_id”, StringType, nullable = true)
.add(“last_event”, eventType, nullable = true)val devicesType = new StructType()
.add(“cameras”, camerasType, nullable = true)val schema = new StructType()
总结
.add(“devices”, devicesType, nullable = true)4.2.4. 使用 Spark 流计算连接 Kafka 数据源
-
目标和步骤
- 创建 Topic 并输入数据到 Topic
- 使用 Spark 读取 Kafka 的 Topic
- 总结
4.2.5. JSON 解析和数据统计
-
目标和步骤
- JSON 解析
val camerasType = new StructType()
.add(“device_id”, StringType, nullable = true)
.add(“last_event”, eventType, nullable = true)val devicesType = new StructType()
.add(“cameras”, camerasType, nullable = true)val schema = new StructType()
.add(“devices”, devicesType, nullable = true)-
-
因为
JSON
中包含Date
类型的数据, 所以要指定时间格式化方式val jsonOptions = Map("timestampFormat" -> "yyyy-MM-dd'T'HH:mm:ss.sss'Z'")
-
使用
from_json
这个UDF
格式化JSON
.select(from_json('value, schema, jsonOptions).alias("parsed_value"))
-
选择格式化过后的
JSON
中的字段因为
JSON
被格式化过后, 已经变为了StructType
, 所以可以直接获取其中某些字段的值.selectExpr("parsed_value.devices.cameras.last_event.has_person as has_person", "parsed_value.devices.cameras.last_event.start_time as start_time")
-
数据处理
val spark = SparkSession.builder()
.master(“local[6]”)
.appName(“kafka integration”)
.getOrCreate()import org.apache.spark.sql.streaming.OutputMode
import org.apache.spark.sql.types._val source = spark
.readStream
.format(“kafka”)
.option(“kafka.bootstrap.servers”, “node01:9092,node02:9092,node03:9092”)
.option(“subscribe”, “streaming-test”)
.option(“startingOffsets”, “earliest”)
.load()val eventType = new StructType()
.add(“has_sound”, BooleanType, nullable = true)
.add(“has_motion”, BooleanType, nullable = true)
.add(“has_person”, BooleanType, nullable = true)
.add(“start_time”, DateType, nullable = true)
.add(“end_time”, DateType, nullable = true)val camerasType = new StructType()
.add(“device_id”, StringType, nullable = true)
.add(“last_event”, eventType, nullable = true)val devicesType = new StructType()
.add(“cameras”, camerasType, nullable = true)val schema = new StructType()
.add(“devices”, devicesType, nullable = true)val jsonOptions = Map(“timestampFormat” -> “yyyy-MM-dd’T’HH:mm:ss.sss’Z’”)
import org.apache.spark.sql.functions._
import spark.implicits._val result = source.selectExpr(“CAST(key AS STRING) as key”, “CAST(value AS STRING) as value”)
.select(from_json('value, schema, jsonOptions).alias(“parsed_value”))
.selectExpr(“parsed_value.devices.cameras.last_event.has_person as has_person”,
“parsed_value.devices.cameras.last_event.start_time as start_time”)
.filter('has_person === true)
.groupBy('has_person, 'start_time)
.count()result.writeStream
运行测试
.outputMode(OutputMode.Complete())
.format(“console”)
.start()
.awaitTermination()5. Sink
-
目标和步骤
5.1. HDFS Sink
-
目标和步骤
- 场景和需求
- 代码实现
val spark = SparkSession.builder()
.master(“local[6]”)
.appName(“kafka integration”)
.getOrCreate()import spark.implicits._
val source = spark
.readStream
.format(“kafka”)
.option(“kafka.bootstrap.servers”, “node01:9092,node02:9092,node03:9092”)
.option(“subscribe”, “streaming-bank”)
.option(“startingOffsets”, “earliest”)
.load()
.selectExpr(“CAST(value AS STRING)”)
.as[String]val result = source.map {
item =>
val arr = item.replace(""", “”).split(";")
(arr(0).toInt, arr(1).toInt, arr(5).toInt)
}
.as[(Int, Int, Int)]
.toDF(“age”, “job”, “balance”)result.writeStream
.format(“parquet”) // 也可以是 “orc”, “json”, “csv” 等
.option(“path”, “/dataset/streaming/result/”)
.start()5.2. Kafka Sink
-
目标和步骤
- 场景
- 代码
val spark = SparkSession.builder()
.master(“local[6]”)
.appName(“kafka integration”)
.getOrCreate()import spark.implicits._
val source = spark
.readStream
.format(“kafka”)
.option(“kafka.bootstrap.servers”, “node01:9092,node02:9092,node03:9092”)
.option(“subscribe”, “streaming-bank”)
.option(“startingOffsets”, “earliest”)
.load()
.selectExpr(“CAST(value AS STRING)”)
.as[String]val result = source.map {
item =>
val arr = item.replace(""", “”).split(";")
(arr(0).toInt, arr(1).toInt, arr(5).toInt)
}
.as[(Int, Int, Int)]
.toDF(“age”, “job”, “balance”)result.writeStream
.format(“kafka”)
.outputMode(OutputMode.Append())
.option(“kafka.bootstrap.servers”, “node01:9092,node02:9092,node03:9092”)
.option(“topic”, “streaming-bank-result”)
.start()
.awaitTermination()5.3. Foreach Writer
-
目标和步骤
- 需求
- 代码
val spark = SparkSession.builder()
.master(“local[6]”)
.appName(“kafka integration”)
.getOrCreate()import spark.implicits._
val source = spark
.readStream
.format(“kafka”)
.option(“kafka.bootstrap.servers”, “node01:9092,node02:9092,node03:9092”)
.option(“subscribe”, “streaming-bank”)
.option(“startingOffsets”, “earliest”)
.load()
.selectExpr(“CAST(value AS STRING)”)
.as[String]val result = source.map {
item =>
val arr = item.replace(""", “”).split(";")
(arr(0).toInt, arr(1).toInt, arr(5).toInt)
}
.as[(Int, Int, Int)]
.toDF(“age”, “job”, “balance”)class MySQLWriter extends ForeachWriter[Row] {
val driver = “com.mysql.jdbc.Driver”
var statement: Statement = _
var connection: Connection = _
val url: String = “jdbc:mysql://node01:3306/streaming-bank-result”
val user: String = “root”
val pwd: String = “root”override def open(partitionId: Long, version: Long): Boolean = {
Class.forName(driver)
connection = DriverManager.getConnection(url, user, pwd)
this.statement = connection.createStatement
true
}override def process(value: Row): Unit = {
statement.executeUpdate(s"insert into bank values(" +
s" v a l u e . g e t A s [ I n t ] ( " a g e " ) , " + s " {value.getAs[Int]("age")}, " + s" value.getAs[Int]("age"),"+s"{value.getAsInt}, " +
s"${value.getAsInt} )")
}override def close(errorOrNull: Throwable): Unit = {
connection.close()
}
}result.writeStream
.foreach(new MySQLWriter)
.start()
.awaitTermination()5.4. 自定义 Sink
-
目标和步骤
- Spark 加载 Sink 流程分析
def shortName(): String (1)
}trait StreamSourceProvider {
def createSource( (2)
sqlContext: SQLContext,
metadataPath: String,
schema: Option[StructType],
providerName: String,
parameters: Map[String, String]): Source
}trait StreamSinkProvider {
def createSink( (3)
sqlContext: SQLContext,
parameters: Map[String, String],
partitionColumns: Seq[String],
outputMode: OutputMode): Sink
}1 提供短名 2 创建 Source
3 创建 Sink
-
自定义
Sink
的方式-
根据前面的流程说明, 有两点非常重要
-
Spark
会自动加载所有DataSourceRegister
的子类, 所以需要通过DataSourceRegister
加载Source
和Sink
-
Spark 提供了
StreamSinkProvider
用以创建Sink
, 提供必要的依赖
-
-
所以如果要创建自定义的
Sink
, 需要做两件事-
创建一个注册器, 继承
DataSourceRegister
提供注册功能, 继承StreamSinkProvider
获取创建Sink
的必备依赖 -
创建一个
Sink
子类
-
-
-
自定义 Sink
val spark = SparkSession.builder()
.master(“local[6]”)
.appName(“kafka integration”)
.getOrCreate()import spark.implicits._
val source = spark
.readStream
.format(“kafka”)
.option(“kafka.bootstrap.servers”, “node01:9092,node02:9092,node03:9092”)
.option(“subscribe”, “streaming-bank”)
.option(“startingOffsets”, “earliest”)
.load()
.selectExpr(“CAST(value AS STRING)”)
.as[String]val result = source.map {
item =>
val arr = item.replace(""", “”).split(";")
(arr(0).toInt, arr(1).toInt, arr(5).toInt)
}
.as[(Int, Int, Int)]
.toDF(“age”, “job”, “balance”)class MySQLSink(options: Map[String, String], outputMode: OutputMode) extends Sink {
override def addBatch(batchId: Long, data: DataFrame): Unit = {
val userName = options.get(“userName”).orNull
val password = options.get(“password”).orNull
val table = options.get(“table”).orNull
val jdbcUrl = options.get(“jdbcUrl”).orNullval properties = new Properties properties.setProperty("user", userName) properties.setProperty("password", password) data.write.mode(outputMode.toString).jdbc(jdbcUrl, table, properties)
}
}class MySQLStreamSinkProvider extends StreamSinkProvider with DataSourceRegister {
override def createSink(sqlContext: SQLContext,
parameters: Map[String, String],
partitionColumns: Seq[String],
outputMode: OutputMode): Sink = {
new MySQLSink(parameters, outputMode)
}override def shortName(): String = “mysql”
}result.writeStream
.format(“mysql”)
.option(“username”, “root”)
.option(“password”, “root”)
.option(“table”, “streaming-bank-result”)
.option(“jdbcUrl”, “jdbc:mysql://node01:3306/test”)
.start()
.awaitTermination()5.5. Tigger
-
目标和步骤
- 微批次处理
import org.apache.spark.sql.functions._
import spark.implicits._spark.sparkContext.setLogLevel(“ERROR”)
val source = spark.readStream
.format(“rate”)
.load()val result = source.select(log10('value) cast IntegerType as 'key, 'value)
.groupBy('key)
.agg(count('key) as 'count)
.select('key, 'count)
.where('key.isNotNull)
.sort('key.asc) -
默认方式划分批次
-
介绍
-
默认情况下的
Structured Streaming
程序会运行在微批次的模式下, 当一个批次结束后, 下一个批次会立即开始处理
步骤
-
-
指定落地到
Console
中, 不指定Trigger
代码
-
-
result.writeStream .outputMode(OutputMode.Complete()) .format("console") .start() .awaitTermination()
-
-
按照固定时间间隔划分批次
-
介绍
-
使用微批次处理数据, 使用用户指定的时间间隔启动批次, 如果间隔指定为
0
, 则尽可能快的去处理, 一个批次紧接着一个批次-
如果前一批数据提前完成, 待到批次间隔达成的时候再启动下一个批次
-
如果前一批数据延后完成, 下一个批次会在前面批次结束后立即启动
-
如果没有数据可用, 则不启动处理
步骤
-
-
-
通过
Trigger.ProcessingTime()
指定处理间隔
代码
-
-
result.writeStream .outputMode(OutputMode.Complete()) .format("console") .trigger(Trigger.ProcessingTime("2 seconds")) .start() .awaitTermination()
-
-
一次性划分批次
-
介绍
-
只划分一个批次, 处理完成以后就停止
Spark
工作, 当需要启动一下Spark
处理遗留任务的时候, 处理完就关闭集群的情况下, 这个划分方式非常实用
步骤
-
-
使用
Trigger.Once
一次性划分批次
代码
-
-
result.writeStream .outputMode(OutputMode.Complete()) .format("console") .trigger(Trigger.Once()) .start() .awaitTermination()
-
-
连续流处理
5.6. 从 Source 到 Sink 的流程
-
目标和步骤
- 从 Source 到 Sink 的流程
5.7. 错误恢复和容错语义
-
目标和步骤
- 端到端
- 三种容错语义
- Sink 的容错
- 容错所需要的存储
- 需要的外部支持
6. 有状态算子
-
目标和步骤
- 状态
- 总结
6.1. 常规算子
-
目标和步骤
- 案例
val result = source.select('Rating, 'MovieID)
总结
.where('Rating > 3)6.2. 分组算子
-
目标和步骤
- 案例
val result = source.select(explode('Genres) as 'Genres)
.groupBy('Genres)
.agg(count('Genres) as 'Count)result.writeStream
总结
.outputMode(OutputMode.Complete())
.format(“console”)
.queryName(“genres_count”)
.start()
.awaitTermination()-
Structured Streaming
不仅支持groupBy
, 还支持groupByKey