小白学习Spark06-Spark Streaming

6.1 简介

  • Spark Streaming 是Spark为了某些需要即时处理收到的数据,比如实时跟踪页面访问统计的应用、训练机器学习模型的应用、自动检测异常的应用。
  • DStream:是Spark Streaming使用 离散化流 的抽象表示,它是随时间推移而收到的数据的序列。
    • 在内部,每个时间区间收到的数据都作为RDD存在,而DStream是由这些RDD所组成的序列。
    • DStream能够从各种输入源创建,比如Flume、Kafka、HDFS等。
    • 支持两种操作
      • 转化操作:会生成一个新的DStream
      • 输出操作:可以把数据写入到外部系统中
  • Spark Streaming应用通过一些额外的配置(设置检查点机制等)可以保证24/7不间断工作
  • Spark Streaming程序
    • 程序 最好以使用Maven或者sbt编译出来的独立应用的形式运行
    • 一个简单程序(从一台服务器的7777端口上收到一个以换行符分隔的多行文本,要从中筛选出包含单词error的行,并打印出来) 的流程
      • Spark Streaming引入Maven索引
      • StreamingContext:流计算功能的主要入口,它在底层创建出SparkContext,用来处理数据;其构造函数还接收用来指定多长时间处理一次新数据的 批次间隔 作为输入
      • socketTextStream:创建出基于本地7777端口上收到的文本数据的DStream。
      • 把DStream通过filter()进行转化,只得到包含“error”的行
      • print():把筛选出来的前10行打印出来。
      • 在上面设定好要进行的计算后,调用 StreamingContext的start()方法即可开始接收数据并进行上述计算,一个StreamingContext只能启动一次。
      • 执行会在另外一个线程中进行,因此需要调用awaitTermination来等待流计算完成,来防止应用退出。
     	// 用Scala进行流式筛选,打印出包含“error”的行
     	// 从SparkConf创建StreamingContext并指定1秒钟的批处理大小
     	val scc = new StreamingContext(conf,Seconds(1))
     	// 连接到本地机器7777端口上后,使用收到的数据创建DStream
     	val lines = ssc.socketTextStream("localhost",7777)
     	// 从DStream中筛选出包含字符串“error”的行
     	val errorLines = lines.filter(_.contains("error"))
     	//打印出有“error”的行
     	errorLines.print()
    	
    	// 用Scala进行流式筛选,打印出包含“error”的行
    	// 启动流计算环境 StreamingContext并等待它“完成”
    	ssc.start()
    	// 等待作业完成
    	ssc.awaitTermination()
    

6.2 架构与抽象

  • 架构:Spark Streaming使用“微批次”的架构,把流式计算当做一系列连续的小规模批处理来对待。
    • Spark Streaming从各种输入源中读取数据,并把数据分组为小的批次,新的批次按均匀的时间间隔创建出来
    • 在每个时间区间开始时,一个新的批次就会被创建出来,在该区间内收到的数据都会被添加到这个批次中,在该区间内收到的数据都会被添加到这个批次中
    • 在时间区间结束时,批次会停止增长
    • 时间区间的大小是由 批次间隔 这个参数决定的,一般设在0.5s~n秒之间,由应用开发者配置。
    • 每个输入批次都会形成一个RDD,以Spark作业的方式处理并生成其他的RDD,处理的结果可以以批处理的方式传给外部系统
    • Spark Streaming的高层次的架构
      在这里插入图片描述
    • DStream:是一个持续的RDD序列,每个RDD代表数据流中一个时间片内的数据。
      在这里插入图片描述
    • 上述例子 DStream及其转化关系
      • 可以看出筛选过的日志每秒钟被打印一次(在创建StreamingContext时设定的批次间隔为 1 秒)
        在这里插入图片描述
  • Spark Streaming在Spark的驱动器程序----工作节点的结构的执行过程
    在这里插入图片描述
    • Spark Streaming为每个输入源启动对应的接收器(接收器以任务的形式运行在应用的执行器进程中),从输入源收集数据并保存为RDD,它们会在收集到输入数据后会把数据复制到另一个执行器进程来保障容错性。
    • 数据保存在执行器进程的内存中(和缓存RDD的方式一样)
    • 驱动器程序中的StreamingContext会周期性地运行Spark作业来处理这些数据,把数据与之前时间区间中的RDD进行整合。
    • DStream的容错性:只要输入数据存在,就可以使用RDD谱系重算出任意状态;默认情况下,收到的数据分别存在两个节点上,这样就可以容忍一个工作节点的故障。
    • 重算的代价比较大,因此需要 检查点机制,把状态阶段性地存储到可靠文件系统(HDFS、S3)中。

6.3 转化操作

  • 两种转化操作
    • 无状态转化操作:每个批次的处理不依赖于之前批次的数据,比如map()、filter()、reduceByKey()等
    • 有状态转化操作:需要使用之前批次的数据或者中间结果来计算当前批次的数据,比如基于滑动窗口的转化操作、追踪状态变化的转化操作。
6.3.1 无状态转化操作
  • 无状态转化操作即把简单的RDD转化操作应用到熬每个批次上,转化DStream中的每一个RDD。
  • DStream无状态转化操作的例子
    在这里插入图片描述
  • 每个DStream在内部是由多个RDD(批次)组成,而无状态转化操作时分别应用到每个RDD上的。
 	// Scala中对DStream使用map()和reduceByKey()
 	// 假设ApacheAccessingLog 是用来从Apache日志中解析条目的工具类
 	val accessLogDStream = logData.map(line => ApacheAccessLog.parseFromLogLine(line))
 	val ipDStream = accessLogsDStream.map(entry => (entry.getIpAddress(),1))
 	val ipCountsDStream = ipDStream.reduceByKey((x,y) => x+y)

	// 无状态转化操作也是能在多个DStream间整合数据
	//在Scala中连接两个DStream
	val ipBytesDStream = accessLogsDStream.map(entry => (entry.getIpAddress(),entry.getContentSize()))
	val ipBytesSumDStream = ipBytesDStream.reduceByKey((x,y) => x+y)
	val ipBytesRequestCountDStream = ipCountsDStream.join(ipBytesSumDStream )
  • transform():允许你对DStream提供任意一个RDD到RDD的函数。这个函数会在数据流中的每个批次中被调用,生成一个新的流。
    • 应用:重用你为RDD写的批处理代码
    • 使用transform()重用extractOutliter()函数(用来从一个日志记录的RDD中提取出异常值的RDD)
    // Scala中对DStream使用transform()
    	val outlierDStream = accessLogsDStream.transform{ rdd 
    			=>extractOutliers(rdd) 
    	}
    
6.3.2 有状态转化操作
  • 有状态转化操作:跨时间区间跟踪数据的操作,即一些先前批次的数据也被用来在新的批次中计算结果。
  • 主要有两种类型
    • 滑动窗口:以一个时间阶段为滑动窗口进行操作
    • updataStateByKey():用来跟踪每个键的状态变化
  • 有状态转化操作 需要在 StreamingContext中打开检查点机制来确保容错性。
  • checkpoint: 设置检查点
6.3.2.1 基于窗口的转化操作
  • 基于窗口的转化操作 会在一个比StreamingContext的批次间隔更长的时间范围内,通过整合多个批次的结果,计算出整个窗口的结果。
  • 基于窗口的转化操作的两个参数:窗口时长、滑动步长,两者都必须是StreamContext的批次间隔的整数倍
    • 窗口时长:控制每次计算最近的多少个批次的数据,最近的windowDuration/batchInterval个批次
    • 滑动步长:默认值跟批次间隔相等,用来控制对新的DStream进行计算的间隔。
  • window():返回一个新的DStream来表示所请求的窗口操作的结果数据,即window()生成的DStream中的每个RDD会包含多个批次中的数据,可以对这些数据进行count()、transform()等操作。
  • 一个基于窗口的流数据
    • 窗口时长为3个批次,滑动步长为2个批次,每隔2个批次就对前3个批次的数据进行一次计算
      在这里插入图片描述
    // 如何在Scala中使用window()对窗口进行计数
    	val accessLogsWindow = accessLogsDStream.window(Seconds(30), Seconds(10))
    	val windowCounts = accessLogsWindow.count()
    	}
    
  • reduceByWindow()、reduceByKeyAndWindow():可以对每个窗口更高效地进行归约操作,接收一个归约函数,在整个窗口上执行。除此之外,有种特殊形式,即通过只考虑新进入窗口的数据和离开窗口的数据,让Spark增量计算归约结果,这种特殊形式需要提供归约函数的一个逆函数,比如 + 对应的逆函数为 - ,在大窗口中,提供逆函数可以大大提高执行效率。
  • 普通的reduceByWindow()和逆函数的增量式reduceByWindow()的区别
  • 在这里插入图片描述
    // Scala中 每个IP地址的访问量计数
    val ipDStream = accessLogsDStream.map(logEntry => (logEntry.getIpAddress(),1))
    val ipCountDStream = ipDStream.reduceByKeyAndWindow(
    	{ (x,y) => x+y}, //加上新进入窗口的批次中的元素
    	{ (x,y) => x-y}, //移除离开窗口的老批次中的元素
    	Seconds(30), //窗口时长
    	Seconds(10) //滑动步长
    )
    
    • countByWindow():对数据进行计数操作,返回一个表示每个窗口中元素个数的DStream
    • countByValueAndWindow():对数据进行计数操作,返回的DStream包含窗口中每个值的个数
    // Scala中 的窗口计数操作
    val ipDStream = accessLogsDStream.map{entry=> entry.getIpAddress()}
    val ipAddressRequestCount = ipDStream.countByValueAndWindow(Seconds(30),Seconds(10))
    val requestCount = accessLogsDStream.countByWindow(Seconds(30),Seconds(10))
    
6.3.2.2 UpdateStateByKey 转化操作
  • updateStateByKey() :提供了对一个状态变量的访问,用于键值对形式的DStream;给定一个由(键,事件)对构成的DStream,并传递一个指定如何根据新的时间更新每个键对应状态的函数,它可以构建出一个新的DStream,其内部数据为(键,状态)对。
    • update(events,oldState):接收与某键相关的时间以及该键之前的对应的状态,返回这个键对应的新状态
      • events: 在当前批次中收到的事件的列表(可能为空)
      • oldState: 是一个可选的状态对象,存放在Option内,如果是一个键没有之前的状态,这个值可以空缺
      • newState:由函数返回,也以Option形式存在,可以返回一个空的Option来表示想要删除该状态
    • updateStateByKey()的结果会是一个新的DStream,其内部的RDD序列是由每个时间区间对应的(键,状态)对组成的。
  • 一个简单的例子:使用updateStateByKey()来追踪日志消息中各HTTP响应代码的计数,这里 键时响应代码,状态时代表各响应代码计数的整数,事件则是页面访问
    // Scala中 使用updateStateByKey()运行响应代码的计数
    def updateRunningSum(values: Seq[Long], state: Option[Long]) = {
    	Some(state.getOrElse(0L) +  values.size)
    }
    val responseCodeDStream = accessLogsDStream.map(log => (log.getResponseCode(),1L))
    val responseCodeCountDStream = responseCodeDStream.updateStateByKey(updateRunningSum _)
    

6.4 输出操作

  • 输出操作:指定了对流数据经转化操作得到的数据所要执行的操作
  • print():在每个批次中抓取DStream的前10个元素打印出来。
  • saveAsTextFiles:通过此来保存结果,接受一个目录作为参数来存储文件,还支持通过可选参数来设置文件的后缀名,每个批次的结果都被保存在给定目录的子目录中,且文件名中含有时间和后缀名。
	//在Scala中将DStream保存为文本文件
	ipAddressRequestCount.saveAsTextFiles("outputDir","txt")
  • saveAsHadoopFiles:接收一个Hadoop输出格式作为参数,保存SequenceFile文件
  //在Scala中将DStream保存为SequenceFile
  val writableIpAddressRequestCount = ipAddressRequestCount.map{
  	(ip, count) => (new Text(ip), new LongWritable(count)) }
  writableIpAddressRequestCount.saveAsHadoopFiles[
  	SequenceFileOutputFormat[Text, LongWritable]]("outputDir","txt")
  • foreachRDD:对DStream中的RDD运行任意计算,可以重用在Spark中实现的所有行动操作。
	//在Scala中使用foreachRDD()将数据存储到外部系统中
	ipAddressRequestCount.foreachRDD{ rdd=>
		rdd.foreachPartition{ partition =>
			// 打开到存储系统的连接(比如一个数据库的连接)
			partition.foreach{ item =>
				//使用连接把item存到系统中
			}
			//关闭连接
		}
	}

6.5 输入源

  • Spark Streaming支持不同的数据源,一些 数据源 已经被打包到Spark Streaming的Maven工件中,而其他的一些则可以通过spark-streaming-kafka等附加工件获取。
6.5.1 核心数据源
  • 套接字 (上面已经论述)
  • 文件流
    • Spark Streaming支持从任意Hadoop兼容的文件系统目录中的文件创建数据流。
    • 要使用Spark Streaming来处理数据,则需要为目录名字提供统一的日期格式,文件也必须原子化创建
    • 读取目录中的文本文件流
	// Scala中 读取目录中的文本文件流
	val logData = ssc.textFileStream(logDirectory)
  • 除了文本数据,也可以读取任意Hadoop输入格式,只需要将Key,Value以及InputFormat类提供给Spark Streaming即可。
	// Scala中 读取目录中的SequenceFile流
	ssc.fileStream[LongWritable, IntWritable
			SequenceFileInputFormat[LongWritable, IntWritable]](inputDirectory).map{
			case (x,y) => (x.get(),y.get())
  • Akka actor流
    • 另一个核心数据源接收器是actorStream,它可以把Akka actor作为数据流的源。
    • 创建一个actor流
      • 创建一个Akka actor
      • 实现 org.apache.spark.streaming.receiver.ActorHelper接口
      • 要把输入数据从actor复制到Spark Streaming中,需要在收到新数据时调用actor的store()函数。
6.5.2 附加数据源
  • 通过附加数据源接收器从一些知名数据获取系统中接收的数据,这些接收器都作为Spark Streaming的组件进行独立打包,需要在构建文件中添加额外的包才能使用它们。
  • Apache Kafka
    • kafkaUtils对象可以在StreamingContext和JavaStreamingContext中以Kafka消息创建出DStream,其DStream由成对的主题和消息组成。
    • 创建一个流数据,需要使用 StreamingContext实例、一个由逗号隔开的Zookeeper主机列表字符串、消费者组的名字、以及一个从主题到针对这个主题的接收器线程数的映射表来调用createStream()方法。
	// Scala中 用Apache Kafka 订阅 Panda主题
	import org.apche.spark.streaming.kafka._
	...
	// 创建一个从主题到接收器线程数的映射表
	val topics = List(("Pandas",1),("logs",1)).toMap
	val topicLines = KafkaUtils.createStream(ssc, zkQuorum,group,topics)
	StreamingLogInput.processLines(topicLines.map(_._2))
  • Apache Flume
    • 两种接收器使用Apache Flume
      • 推式接收器:以Avro数据池的方式工作,由Flume向其中推数据
      • 拉式接收器:从自定义的中间数据池中拉数据,向其他进程可以使用Flume把数据推进该中间数据池。
    • 以上两种方式都需要重新配置Flume,并在某个节点配置的端口上运行接收器,要使用其中的任何一种方法,都需要在工程中引入Maven工件。
    • Flume接收器选项
      在这里插入图片描述
  • 推式接收器
    • 配置Flume来把数据发到Avro数据池,因为推式接收器以Avro数据池的方式工作,而不是使用事务来接收数据。
    • 缺点:没有事务支持,会增加运行接收器的工作节点发生错误时丢失少量数据的几率。
  • 拉式接收器
    • 它设置一个专门的Flume数据池供Spark Streaming读取,并让接收器主动从数据池中拉取数据
    • 优点:有事务支持,弹性较好,Spark Streaming通过事务从数据池中读取并复制数据,在收到事务完成的通知前,这些数据还会保留在数据池中。
  • 自定义输入源
    • 实现自己的接收器来支持别的输入源
6.5.3 多数据源与集群规模
  • 使用多个接收器对于提高聚合操作汇总的数据获取的吞吐量是对性能有很大提高的,不同的接收器也可以去不同的输入源中接收各种数据,然后使用join或cogroup进行整合。
  • 接收器 都是以Spark执行器程序中一个长期运行的任务的形式进行的,因此会占据分配给应用的CPU核心,还要预留多余的CPU核心来处理数据,因此,要运行多个接收器,则必须至少有和接收器数目相同的核心数+用来完成计算所需要的核心数。

6.6 24/7不间断运行

  • 不间断运行Spark Streaming应用,需要以下配置
    • 设置好诸如 HDFS或者Amazon S3等可靠的存储系统中的检查点机制
    • 考虑驱动器程序的容错性
    • 对不可靠输入源的处理
6.6.1 检查点机制
  • 检查点机制是Spark Streaming用来保障容错性的主要机制,它可以使用Spark Streaming阶段性地把应用数据存储到HDFS或者Amazon S3等可靠的存储系统中,以供恢复时使用。
  • 检查点机制的两个目的服务:
    • 控制发生失败时需要重算的状态数,Spark Streaming可以通过转化图的谱系图来重算状态,检查点机制可以控制需要在转化图中回溯多远
    • 提供驱动器程序容错。如果流计算应用中驱动器程序奔溃,可以重启驱动器程序并让驱动器程序从检查点回复,这样Spark Streaming就可以读取之前运行的程序处理数据的进度,并从那里继续。
6.6.2 驱动器程序容错
  • 需要以特殊的方式创建StreamingContext来达到驱动器程序容错的效果。
	// Scala中 配置一个可以从错误中恢复的驱动器程序
	def createStreamingContext(){
		...
		val sc = new SparkContext(conf)
		//以1秒作为批次大小创建StreamingContext
		val ssc = new StreamingContext(sc.Seconds(1))
		ssc.checkpoint(checkpointDir)
	}
	...
	val ssc = StreamingContext.getOrCreate(checkpointDir,createStreamingContext _)
  • 这段代码运行时,如果检查点目录不存在,则StreamingContext会在你调用工厂函数(createStreamingContext())时把目录创建出来,此时需要设置检查点目录。假设驱动器程序失败,你要重启驱动器程序并再次执行代码,getOrCreate()会重新从检查点目录中初始化StreamingContext,然后继续处理
  • 当驱动器程序崩溃时,Spark的执行器进程也会重启。
6.6.3 工作节点容错
  • 所有从外部数据源中收到的数据都在多个工作节点中备份,即使数据丢失,也可以根据RDD谱系图将丢失的数据从幸存的输入数据备份中重算出来。
6.6.4 接收器容错
  • 如果运行接收器的工作节点发生错误,Spark Streaming会在集群中别的节点上重启失败的接收器,这种情况会不会导致数据的丢失取决于数据源的行为(数据源是否会重发数据)以及接收器的实现(接收器是否会向数据源确认收到的数据)
  • 接收器提供一下保证:
    • 所有从可靠文件系统中读取的数据都是可靠的,因为底层的文件系统是有备份的。Spark Streaming会在应用崩溃猴从检查点出继续执行
    • 对于像Kafka、推式Flume、Twitter这样不可靠的数据源,Spark会把输入数据复制到其他节点上,但是如果接收器任务崩溃,Spark还是会丢失数据
  • 综上所述,确保所有数据都被处理的最佳方式是 使用可靠的数据源(HDFS、拉式Flume等)
6.6.5 处理保证
  • 主要是对外部系统的数据进行保证,通过使用事务操作来写入外部系统(即原子化地将一个RDD分区一次写入)或设计幂等的更新操作(即多次运行同一个更新操作仍生成相同的结果),自动将其原子化地移动到最终位置,以此确保每个输出文件都只存在一份。

6.7 Streaming用户节点

  • 4040端口,可以查看Streaming用户界面
  • Streaming用户界面 展示了批处理和接收器的统计信息。

6.8 性能考量

6.8.1 批次和窗口大小
  • Spark Streaming可以使用的最小批次间隔 是500毫秒
  • 寻找最小批次大小的最佳实践是从一个较大的批次大小,不断使用更小的批次大小,观测用户界面中显示的处理时间,当处理时间达到最小时,则对应的批次也是最小的
  • 滑动步长类似
6.8.2 并行度
  • 三种方式提高并行度
    • 增加接收器数目
    • 将收到的数据显式地重新分区
    • 提高聚合计算的并行度
6.8.3 垃圾回收和内存使用
  • java的垃圾回收机制(GC)
  • 通过打开JAVA的并发标志-清楚收集器来减少GC引发的不可预测的长暂停
  • 把RDD以序列化的格式缓存也可以减少GC的压力
  • 主动从缓存中移除不大可能再次使用的RDD,可以减少GC的压力。





  • 关注「一个热爱学习的计算机小白」公众号 ,对作者的小小鼓励,后续更多资源敬请关注。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值