Spark Streaming

Spark Streaming简介
  • Spark StreamingSpark为了处理实时流数据而设计的模型,允许基于批处理API进行对实时流数据进行处理。
  • Spark Streaming使用离散化流(discretized stream)作为抽象表示,叫做DStream。类似于Spark中的RDD,用于存储实时流数据。DStream是将实时流数据分批整合成RDD,是RDD组成的序列。所以DStream支持转化操作(RDD之间的转换)与输出操作(将数据写入外部系统)。
  • Spark Streaming因为是处理实时流数据,所以需要保证7*24不间断工作,为了保证任务的容错性,会提供检查点机制。
Spark Streaming架构与抽象
  • Spark Streaming采用微批次的架构作为处理流数据的方式。Spark Streaming从各种输入源中读取数据,每个新的批次是一个时间间隔内接收到的数据,一个批次的数据作为一个RDD对象,可以通过控制批次间隔的参数控制每次批处理数据RDD的大小。一般是几百毫秒到几秒之间。
graph LR
	source[输入源]
	subgraph Streaming
		B[接收器] --> C[DStream]
	end	
	source --> B
	C --> D[输出结果]
  • DStream数据结构

DStream





时间0到1的数据

时间1到2的数据

时间2到3的数据

时间3到4的数据

时间4到5的数据


Spark Streaming转化操作
  • Spark Streaming根据批次处理是否依赖于之前批次数据的条件,将转化操作分为有状态(依赖)和无状态(不依赖)两种。
无状态
  • 无状态转化操作可以看做是分别对每个批次的RDD单独操作,每个批次之间相互独立。
  • 支持的常见API

函数名称

目的

示例

map()

转换元素格式或取值

ds.map(x => x+1)

flatMap()

元素切分为多个元素

ds.flatMap(x => x.split(" "))

filter()

返回制定条件的元素

ds.filter(x => x != "")

repartition()

修改分区数

ds.repartition(10)

reduceByKey()

键值对数据类型处理相同key

ds.reduceByKey((x,y) => x+y)

groupByKey()

键值对数据类型相同key的值分组

ds.groupByKey()

有状态
  • 窗口时长:每次计算最近多少个批次的数据,如果一个批次的时间间隔为10秒,如果窗口时长为30表示每次计算3个批次的数据。
  • 滑动步长:滑动步长默认值与批次间隔一样,表示每隔多少时间,进行一次计算,如果期望每隔两个批次计算一次,则滑动步长时间为批次间隔的二倍。
  • 窗口时长为30秒,滑动步长为20秒,批次间隔为10秒的数据处理示意图:(每隔两个批次对前三个批次进行计算)









t1

r1

t2

r2

t3

t4

r3

t5

t6


  • 常用API简介
//linesTimes数据格式为 (key,1)表示流数据获取中每个key值出现的个数 默认为1
val lineTimes = lines.map(x => (x,1))
lineTimes.reduceByKeyAndWindow(
  {(x,y) => x+y}, //对新入窗口的批次 相同key次数相加
  {(x,y) => x-y}, //对离开窗口的批次 相同key次数相减
  Seconds(30),  //窗口时长30秒 6个批次
  Seconds(5)  //滑动步长5秒 每隔一次批次就计算一次
)

API

含义

countByWindow()

返回每个窗口中元素个数

countByValueAndWindow()

返回每个窗口中值对应的个数

  • updateStateByKey()

操作键值对格式的DStream,并进行无限增长的计算。

def main(args: Array[String]): Unit = {
    
    /*
    values : 表示当前批次中相同key值 的value值集合
    state :  表示当前key值的之前状态
    */
    def updateStateMethod(values:Seq[Int],state:Option[Int]) ={
      //创建一个变量,用于记录单词出现次数
      var newValue=state.getOrElse(0) //getOrElse相当于if....else.....
      for(value <- values){
        newValue +=value //将单词出现次数累计相加
      }
      Option(newValue)
    }

    //连接nc(netcat)服务,接收数据源,产生Dtream 对象
    val lines=ssc.socketTextStream("localhost",7777)

    //分隔单词,并将分隔后的每个单词出现次数记录为1
    val pairs=lines.flatMap(_.split(" "))
      .map(word=>(word,1))

    //调用updateStateByKey算子,统计单词在全局中出现的次数
    val result = pairs.updateStateByKey[Int](updateStateMethod)
    //result : DStream[scala.Tuple2[String, Int]]
    
    ssc.start()
    ssc.awaitTermination()
}
Spark Streaming输出操作
  • print()

默认打印每个批次的前10行

  • saveAsTextFiles("outputDir", "txt")

将内容按批次保存成文本文件

  • foreachRDD{}

获取每个批次RDD,将RDD自定义存储到外部系统

7*24不间断工作
检查点机制

为了保证Spark Streaming的容错性,我们将Spark Streaming中的应用数据阶段性的存储到可靠的文件系统中。即便Spark自带谱系图实现RDD数据丢失的容错性,Spark Streaming还是从性能角度设置了检查点机制,直接将获取的RDD数据保存在可靠的文件系统中,保证数据的容错性。

ssc.checkpoint("文件系统路径")
驱动器程序容错
def createStreamingContext(): StreamingContext = {
    val sc = new SparkContext(conf)
    val ssc = new StreamingContext(sc, Seconds(1))
    ssc.checkpoint(checkpointDir)
    ssc
}

val ssc = StreamingContext.getOrCreate(checkpointDir, createStreamingContext)
  • 当驱动器程序失败后,重启驱动器程序,getOrCreate方法会从检查点目录的位置初始化StreamingContext对象,继续处理。
工作节点容错
  • 所有从外部节点获取到的数据会在多个节点中备份,包括基于转化操作生成的RDD都允许一个节点的失败。
接收器容错
  • 当接收器节点上的发生错误,Spark Streaming会在别的节点上重启失败的接收器。对于重启过程中接收失败的数据,是否会丢失,取决于上游数据源是否具有事务机制。
处理保证
  • 因为任务某种原因,输出操作会执行多次,这时需要保证输出到外部系统的代码部分具有事务,保证同一条数据在多次插入过程中生成一条结果。例如,向mysql插入数据时,先查询唯一列的值是否存在。
性能考量
批次与窗口大小
  • 当处理时间不变时尽量使用更小的批次,保证内存的可用性。一般情况下500毫秒为较好的最小批次大小。10秒为一个较大的批次大小,
并行度
  • 增加接收器数量

创建多个DStream(每个DStream会创建一个新的接收器),然后使用union将数据合并为同一个数据源。

  • 将接收到的数据显式分区
DStream.repartition(分区数)
  • 提高聚合计算的并行度
val init = sc.parallelize(List("1 2","3 4","3 6"))
val kv = init.map(x => (x.split(" ")(0).toInt, x.split(" ")(1).toInt))
//(1,2),(3,10)
println(kv.reduceByKey((x, y) => x+y, 10).collect().mkString(","))