SparkStreaming简介
-
用于流式数据的处理
-
Spark Streaming具有有高吞吐量和容错能力强等特点
-
支持多数据源输入,eg:Kafka,Flume,Twitter和TCP套接字等
-
数据输入之后可以使用高阶API
-
处理结果可保存到多存储介质中
-
SparkStreaming能与MLlib以及Graphx融合
-
Spark Streaming使用离散化流(Discretized Stream)作为抽象表示,称为DStream。
-
DStream是随时间推移而收到的数据的序列
-
DStream 可以从Flume,Kafka,HDFS等多种输入源创建
-
DStream 支持两种操作:
-
转化操作,会生成一个新的DStream
-
输出操作(output operation),把数据写入外部系统中
-
DStream 提供了许多与 RDD 所支持的操作相类似的操作支持,还增加了与时间相关的新操作,比如滑动窗口
架构
- Spark Streaming使用 mini-batch 的架构,把流式计算当作一系列连续的小规模批处理来对待
- Spark Streaming的编程抽象是离散化流,也就是DStream。它是一个 RDD 序列,每个RDD代表数据流中一个时间片内的数据
- 应用于 DStream 上的转换操作都会转换为底层RDD上的操作
- 这些底层的RDD转换是由Spark引擎完成的,开发人员使用高级API完成数据操作。
- Spark Streaming为每个输入源启动对应的接收器。接收器运行在Executor中,从输入源收集数据并保存为 RDD
- 默认情况下接收到的数据后会复制到另一个Executor中,进行容错;
- Driver 中的 StreamingContext 会周期性地运行 Spark 作业来处理这些数据。
Spark Streaming 运行流程
- 1、客户端提交Spark Streaming作业后启动Driver,Driver启动Receiver,Receiver接收数据源的数据
- 2、每个作业包含多个Executor,每个Executor以线程的方式运行task,SparkStreaming至少包含一个receiver task(一般情况下)
- 3、Receiver接收数据后生成Block,并把BlockId汇报给Driver,然后备份到另外一个 Executor 上
- 4、ReceiverTracker维护 Reciver 汇报的BlockId
- 5、Driver定时启动JobGenerator,根据Dstream的关系生成逻辑RDD,然后创建Jobset,交给JobScheduler
- 6、JobScheduler负责调度Jobset,交给DAGScheduler,DAGScheduler根据逻辑RDD,生成相应的Stages,每个stage包含一到多个Task,将TaskSet提交给TaskSchedule
- 7、TaskScheduler负责把 Task 调度到 Executor 上,并维护 Task 的运行状态
优缺点
优点
- Spark Streaming 内部的实现和调度方式高度依赖 Spark 的 DAG 调度器和RDD,所以SparkStreaming必须是粗粒度方式的设计。同时,由于 Spark 内部调度器足够快速和高效,可以快速地处理小批量数据,这就获得准实时的特性
- Spark Streaming 的粗粒度执行方式使其确保 ”处理且仅处理一次” 的特性(EOS),同时也可以更方便地实现容错恢复机制
- 由于 Spark Streaming 的 DStream 本质是 RDD 在流式数据上的抽象,因此基于 RDD 的各种操作也有相应的基于 DStream 的版本,可降低了学习成本,并能在spark的基础上快速掌握streaming
- 由于 DStream 是在 RDD 上的抽象,那么也就更容易与 RDD 进行交互操作,能够方便的将流式数据和批处理数据结合进行分析
缺点
- Spark Streaming 的粗粒度处理方式也造成了不可避免的延迟。在细粒度处理方式下,理想情况下每一条记录都会被实时处理,而在 Spark Streaming 中,数据需要汇总到一定的量后再一次性处理,这就增加了数据处理的延迟,这种延迟是由框架的设计引入的,并不是由网络或其他情况造成的
Structured Streaming
-
Spark Streaming计算逻辑是把数据按时间划分为DStream,存在以下问题
-
框架自身只能根据 Batch Time 单元进行数据处理,很难处理基于eventtime(即时间戳)的数据,很难处理延迟,乱序的数据
-
流式和批量处理的 API 不完全一致,两种使用场景中,程序代码还是需要一定的转换
-
端到端的数据容错保障逻辑需要用户自己构建,难以处理增量更新和持久化存储等一致性问题
-
基于以上问题,提出了下一代 Structure Streaming 。将数据源映射为一张无界长度的表,通过表的计算,输出结果映射为另一张表
-
以结构化的方式去操作流式数据,简化了实时计算过程,同时还复用了 Catalyst 引擎来优化SQL操作。此外还能支持增量计算和基于event time的计算。
DStream基础数据源
文件数据流:通过 textFileStream(directory) 方法进行读取 HDFS 兼容的文件系统文件
Spark Streaming 将会监控 directory 目录,并不断处理移动进来的文件
- 不支持嵌套目录
- 文件需要有相同的数据格式
- 文件进入 directory 的方式需要通过移动或者重命名来实现
- 一旦文件移动进目录,则不能再修改,即便修改了也不会读取新数据
- 文件流不需要接收器(receiver),不需要单独分配CPU核
Socket数据流
RDD队列流:可使用streamingContext.queueStream(queueOfRDD) 创建基于RDD队列的DStream
DStream转换操作
DStream上的操作与RDD的类似,分为 Transformations(转换)和 OutputOperations(输出)两种,此外转换操作中还有一些比较特殊的方法,如:updateStateByKey、transform 以及各种 Window 相关的操作
注意
- 在DStream与RDD上的转换操作非常类似(无状态的操作)
- DStream有自己特殊的操作(窗口操作、追踪状态变化操作)
- 在DStream上的转换操作比RDD上的转换操作少
DStream 的转化操作可以分为 无状态(stateless) 和 有状态(stateful) 两种:
- 无状态转化操作。每个批次的处理不依赖于之前批次的数据。常见的 RDD 转化操作,例如 map、filter、reduceByKey 等
- 有状态转化操作。需要使用之前批次的数据 或者是 中间结果来计算当前批次的数据。有状态转化操作包括:基于滑动窗口的转化操作 或 追踪状态变化的转化操作
无状态转换
- 无状态转化操作就是把简单的 RDD 转化操作应用到每个批次上,也就是转化
DStream 中的每一个 RDD - 常见的无状态转换包括:map、flatMap、filter、repartition、reduceByKey、
groupByKey;直接作用在DStream上
有状态转换
-
有状态的转换主要有两种:窗口操作、状态跟踪操作
-
窗口操作
-
Window Operations可以设置窗口大小和滑动窗口间隔来动态的获取当前Streaming的状态。
-
基于窗口的操作需要两个参数:
- 窗口长度(windowDuration)。控制每次计算最近的多少个批次的数据
- 滑动间隔(slideDuration)。用来控制对新的 DStream 进行计算的间隔
-
两者都必须是 StreamingContext 中批次间隔(batchDuration)的整数倍
-
updateStateByKey(状态追踪操作),主要功能
-
为Streaming中每一个Key维护一份state状态,state类型可以是任意类型的,可以是自定义对象;更新函数也可以是自定义的
-
通过更新函数对该key的状态不断更新,对于每个新的batch而言,SparkStreaming会在使用updateStateByKey 的时候为已经存在的key进行state的状态更新
-
使用 updateStateByKey 时要开启 checkpoint 功能
DStream输出操作
如果 StreamingContext 中没有设定输出操作,整个流式作业不会启动。
通用的输出操作 foreachRDD,用来对 DStream 中的 RDD 进行任意计算。在foreachRDD中,可以重用 Spark RDD 中所有的 Action 操作。需要注意的:
- 连接不要定义在 Driver 中
- 连接定义在 RDD的 foreach 算子中,则遍历 RDD 的每个元素时都创建连接,得不偿失
- 应该在 RDD的 foreachPartition 中定义连接,每个分区创建一个连接
- 可以考虑使用连接池
SparkStreaming与Kafka整合
针对不同的spark、kafka版本,集成处理数据的方式分为两种:Receiver Approach和Direct Approach
不同集成版本处理方式的支持,如下图所示
对Kafka的支持分为两个版本08(在高版本中将被废弃)、010,两个版本不兼容
Kafka-08 接口
-
Receiver based Approach
-
Kafka-08 接口(Receiver方式):
- Offset保存在ZK中,系统管理
- 对应Kafka的版本 0.8.2.1+
- 接口底层实现使用 Kafka 旧版消费者高阶API
- DStream底层实现为BlockRDD
-
Kafka-08 接口(Receiver with WAL)
- 增强了故障恢复的能力
- 接收的数据与Dirver的元数据保存到HDFS
- 增加了流式应用处理的延迟
-
Direct Approach
-
Direct Approach是 Spark Streaming不使用Receiver集成kafka的方式,在企业生产环境中使用较多。相较于Receiver,有以下特点:
- 不使用 Receiver。减少不必要的CPU占用;减少了 Receiver接收数据写入BlockManager,然后运行时再通过blockId、网络传输、磁盘读取等来获取数据的整个过程,提升了效率;无需WAL,进一步减少磁盘IO;
- Direct方式生的RDD是KafkaRDD,它的分区数与 Kafka 分区数保持一致,便于把控并行度
- 注意:在 Shuffle 或 Repartition 操作后生成的RDD,这种对应关系会失效
- 可以手动维护offset,实现 Exactly Once 语义
-
Kafka-010 接口
-
LocationStrategies(本地策略)
-
添加依赖
org.apache.spark spark-streaming-kafka-0- 10_2.12 ${spark.version} -
LocationStrategies.PreferBrokers:如果 Executor 在 kafka 集群中的某些节点上,可以使用这种策略。此时Executor 中的数据会来自当前broker节点
-
LocationStrategies.PreferConsistent:大多数情况下使用的策略,将Kafka分区均匀的分布在Spark集群的 Executor上
-
LocationStrategies.PreferFixed:如果节点之间的分区有明显的分布不均,使用这种策略。通过一个map指定将 topic 分区分布在哪些节点中
-
ConsumerStrategies(消费策略)
- ConsumerStrategies.Subscribe,用来订阅一组固定topic
- ConsumerStrategies.SubscribePattern,使用正则来指定感兴趣的topic
- ConsumerStrategies.Assign,指定固定分区的集合
- 以上策略都有重载构造函数,允许指定特定分区的起始偏移量;使用 Subscribe
-
或 SubscribePattern 在运行时能实现分区自动发现
-
Offset 管理
-
获取偏移量(Obtaining Offsets)
- stream.foreachRDD { rdd =>
val offsetRanges =
rdd.asInstanceOf[HasOffsetRanges].offsetRanges
rdd.foreachPartition { iter =>
val o: OffsetRange =
offsetRanges(TaskContext.get.partitionId)
println(s"{o.topic}o.topic{o.partition} {o.fromOffset} <br />o.fromOffset<br/>{o.untilOffset}")
}
}
- stream.foreachRDD { rdd =>
-
存储偏移量(Storing Offsets)
-
如果要实现EOS语义(Exactly Once Semantics),必须在幂等的输出之后存储偏移量或者 将存储偏移量与输出放在一个事务中。
-
存储偏移量的方式
-
Checkpoint:Checkpoint是对Spark Streaming运行过程中的元数据和每RDDs的数据状态保存到一个持久化系统中,当然这里面也包含了offset,一般是HDFS、S3,如果应用程序或集群挂了,可以迅速恢复
-
Kafka:默认情况下,消费者定期自动提交偏移量,它将偏移量存储在一个特殊的Kafka主题中(__consumer_offsets)
- 可以将 enable.auto.commit 设置为 false ,在 Streaming 程序输出结果之后,手动提交偏移到kafka。
-
自定义存储
- Offsets可以通过多种方式来管理,但是一般来说遵循下面的步骤:
- 在 DStream 初始化的时候,需要指定每个分区的offset用于从指定位置读取数据
- 读取并处理消息
- 处理完之后存储结果数据
- 用虚线圈存储和提交offset,强调用户可能会执行一系列操作来满足他们更加严格的语义要求。这包括幂等操作和通过原子操作的方式存储offset
- 将 offsets 保存在外部持久化数据库如 HBase、Kafka、HDFS、ZooKeeper、Redis、MySQL … …
-
-
Redis管理的Offset
-
要想将Offset保存到外部存储中,关键要实现以下几个功能:
- Streaming程序启动时,从外部存储获取保存的Offsets(执行一次) - 在foreachRDD中,每个批次数据处理之后,更新外部存储的offsets(多次执行) - 引入依赖 <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency>
-