Spark Streaming实例
转载地址:http://www.aboutyun.com/thread-8901-1-1.html
这一章要讲Spark Streaming,讲之前首先回顾下它的用法,具体用法请参照《Spark Streaming编程讲解 》。
Example代码分析
- val ssc = new StreamingContext(sparkConf, Seconds(1));
- // 获得一个DStream负责连接 监听端口:地址
- val lines = ssc.socketTextStream(serverIP, serverPort);
- // 对每一行数据执行Split操作
- val words = lines.flatMap(_.split(" "));
- // 统计word的数量
- val pairs = words.map(word => (word, 1));
- val wordCounts = pairs.reduceByKey(_ + _);
- // 输出结果
- wordCounts.print();
- ssc.start(); // 开始
- ssc.awaitTermination(); // 计算完毕退出
复制代码
1、首先实例化一个StreamingContext
2、调用StreamingContext的socketTextStream
3、对获得的DStream进行处理
4、调用StreamingContext是start方法,然后等待
我们看StreamingContext的socketTextStream方法吧。
- def socketTextStream(
- hostname: String,
- port: Int,
- storageLevel: StorageLevel = StorageLevel.MEMORY_AND_DISK_SER_2
- ): ReceiverInputDStream[String] = {
- socketStream[String](hostname, port, SocketReceiver.bytesToLines, storageLevel)
- }
复制代码
1、StoageLevel是StorageLevel.MEMORY_AND_DISK_SER_2
2、使用SocketReceiver的bytesToLines把输入流转换成可遍历的数据
继续看socketStream方法,它直接new了一个
- new SocketInputDStream[T](this, hostname, port, converter, storageLevel)
复制代码
继续深入挖掘SocketInputDStream,追述一下它的继承关系,SocketInputDStream>>ReceiverInputDStream>>InputDStream>>DStream。
具体实现ReceiverInputDStream的类有好几个,基本上都是从网络端来数据的。
它实现了ReceiverInputDStream的getReceiver方法,实例化了一个SocketReceiver来接收数据。
SocketReceiver的onStart方法里面调用了receive方法,处理代码如下:
- socket = new Socket(host, port)
- val iterator = bytesToObjects(socket.getInputStream())
- while(!isStopped && iterator.hasNext) {
- store(iterator.next)
- }
复制代码
1、new了一个Socket来结束数据,用bytesToLines方法把InputStream转换成一行一行的字符串。
2、把每一行数据用store方法保存起来,store方法是从SocketReceiver的父类Receiver继承而来,内部实现是:
- def store(dataItem: T) {
- executor.pushSingle(dataItem)
- }
复制代码
executor是ReceiverSupervisor类型,Receiver的操作都是由它来处理。这里先不深纠,后面我们再说这个pushSingle的实现。
到这里我们知道lines的类型是SocketInputDStream,然后对它是一顿的转换,flatMap、map、reduceByKey、print,这些方法都不是RDD的那种方法,而是DStream独有的。
讲到上面这几个方法,我们开始转入DStream了,flatMap、map、reduceByKey、print方法都涉及到DStream的转换,这和RDD的转换是类似的。我们讲一下reduceByKey和print。
reduceByKey方法和RDD一样,调用的combineByKey方法实现的,不一样的是它直接new了一个ShuffledDStream了,我们接着看一下它的实现吧。
- override def compute(validTime: Time): Option[RDD[(K,C)]] = {
- parent.getOrCompute(validTime) match {
- case Some(rdd) => Some(rdd.combineByKey[C](createCombiner, mergeValue, mergeCombiner, partitioner, mapSideCombine))
- case None => None
- }
- }
复制代码
在compute阶段,对通过Time获得的rdd进行reduceByKey操作。接下来的print方法也是一个转换:
- new ForEachDStream(this, context.sparkContext.clean(foreachFunc)).register()
复制代码
打印前十个,超过10个打印"..."。需要注意register方法。
- ssc.graph.addOutputStream(this)
复制代码
启动过程分析
- def start(): Unit = synchronized {
- // 接受到JobSchedulerEvent就处理事件
- eventActor = ssc.env.actorSystem.actorOf(Props(new Actor {
- def receive = {
- case event: JobSchedulerEvent => processEvent(event)
- }
- }), "JobScheduler")
- listenerBus.start()
- receiverTracker = new ReceiverTracker(ssc)
- receiverTracker.start()
- jobGenerator.start()
- }
复制代码
1、启动了一个Actor来处理JobScheduler的JobStarted、JobCompleted、ErrorReported事件。
2、启动StreamingListenerBus作为监听器。
3、启动ReceiverTracker。
4、启动JobGenerator。
我们接下来看看ReceiverTracker的start方法。
- def start() = synchronized {if (!receiverInputStreams.isEmpty) {
- actor = ssc.env.actorSystem.actorOf(Props(new ReceiverTrackerActor), "ReceiverTracker")
- receiverExecutor.start()
- }
- }
复制代码
1、首先判断了一下receiverInputStreams不能为空,那receiverInputStreams是怎么时候写入值的呢?答案在SocketInputDStream的父类InputDStream当中,当实例化InputDStream的时候会在DStreamGraph里面添加InputStream。
- abstract class InputDStream[T: ClassTag] (@transient ssc_ : StreamingContext) extends DStream[T](ssc_) {
- ssc.graph.addInputStream(this)
- //....
- }
复制代码
2、实例化ReceiverTrackerActor,它负责RegisterReceiver(注册Receiver)、AddBlock、ReportError(报告错误)、DeregisterReceiver(注销Receiver)等事件的处理。
3、启动receiverExecutor(实际类是ReceiverLauncher,这名字起得。。),它主要负责启动Receiver,start方法里面调用了startReceivers方法吧。
- private def startReceivers() {
- // 对应着上面的那个例子,getReceiver方法获得是SocketReceiver
- val receivers = receiverInputStreams.map(nis => {
- val rcvr = nis.getReceiver()
- rcvr.setReceiverId(nis.id)
- rcvr
- })
- // 查看是否所有的receivers都有优先选择机器,这个需要重写Receiver的preferredLocation方法,目前只有FlumeReceiver重写了
- val hasLocationPreferences = receivers.map(_.preferredLocation.isDefined).reduce(_ && _)
- // 创建一个并行receiver集合的RDD, 把它们分散到各个worker节点上
- val tempRDD =
- if (hasLocationPreferences) {
- val receiversWithPreferences = receivers.map(r => (r, Seq(r.preferredLocation.get)))
- ssc.sc.makeRDD[Receiver[_]](receiversWithPreferences)
- } else {
- ssc.sc.makeRDD(receivers, receivers.size)
- }
- // 在worker节点上启动Receiver的方法,遍历所有Receiver,然后启动
- val startReceiver = (iterator: Iterator[Receiver[_]]) => {
- if (!iterator.hasNext) {
- throw new SparkException("Could not start receiver as object not found.")
- }
- val receiver = iterator.next()
- val executor = new ReceiverSupervisorImpl(receiver, SparkEnv.get)
- executor.start()
- executor.awaitTermination()
- }
- // 运行这个重复的作业来确保所有的slave都已经注册了,避免所有的receivers都到一个节点上
- if (!ssc.sparkContext.isLocal) {
- ssc.sparkContext.makeRDD(1 to 50, 50).map(x => (x, 1)).reduceByKey(_ + _, 20).collect()
- }
- // 把receivers分发出去,启动
- ssc.sparkContext.runJob(tempRDD, startReceiver)
- }
复制代码
保存接收到的数据
- // 这是ReceiverSupervisorImpl的方法
- def pushSingle(data: Any) {
- blockGenerator += (data)
- }
- // 这是BlockGenerator的方法
- def += (data: Any): Unit = synchronized {
- currentBuffer += data
- }
复制代码
我们看一下它的start方法吧。
- def start() {
- blockIntervalTimer.start()
- blockPushingThread.start()
- }
复制代码
- while (!stopped) {
- clock.waitTillTime(nextTime)
- callback(nextTime)
- prevTime = nextTime
- nextTime += period
- }
复制代码
每隔一段时间就执行callback函数,callback函数是new的时候传进来的,是BlockGenerator的updateCurrentBuffer方法。
- private def updateCurrentBuffer(time: Long): Unit = synchronized {
- try {
- val newBlockBuffer = currentBuffer
- currentBuffer = new ArrayBuffer[Any]
- if (newBlockBuffer.size > 0) {
- val blockId = StreamBlockId(receiverId, time - blockInterval)
- val newBlock = new Block(blockId, newBlockBuffer)
- blocksForPushing.put(newBlock)
- }
- } catch {case t: Throwable =>
- reportError("Error in block updating thread", t)
- }
- }
复制代码
- spark.streaming.blockInterval 默认值是200
- spark.streaming.blockQueueSize 默认值是10
复制代码
这是前面提到的间隔时间和队列的长度,间隔时间默认是200毫秒,队列是最多能容纳10个Block,多了就要阻塞了。
我们接下来看一下BlockGenerator另外启动的那个线程执行的keepPushingBlocks方法到底在干什么?
- private def keepPushingBlocks() {
- while(!stopped) {
- Option(blocksForPushing.poll(100, TimeUnit.MILLISECONDS)) match {
- case Some(block) => pushBlock(block)
- case None =>
- }
- }
- // ...退出之前把剩下的也输出去了
- }
复制代码
它在把blocksForPushing中的block不停的拿出来,调用pushBlock方法,这个方法属于在实例化BlockGenerator的时候,从ReceiverSupervisorImpl传进来的BlockGeneratorListener的。
- private val blockGenerator = new BlockGenerator(new BlockGeneratorListener {
- def onError(message: String, throwable: Throwable) {
- reportError(message, throwable)
- }
- def onPushBlock(blockId: StreamBlockId, arrayBuffer: ArrayBuffer[_]) {
- pushArrayBuffer(arrayBuffer, None, Some(blockId))
- }
- }, streamId, env.conf)
复制代码
- def pushArrayBuffer(arrayBuffer: ArrayBuffer[_], optionalMetadata: Option[Any], optionalBlockId: Option[StreamBlockId]
- ) {
- val blockId = optionalBlockId.getOrElse(nextBlockId)
- val time = System.currentTimeMillis
- blockManager.put(blockId, arrayBuffer.asInstanceOf[ArrayBuffer[Any]], storageLevel, tellMaster = true)
- reportPushedBlock(blockId, arrayBuffer.size, optionalMetadata)
- }
复制代码
处理接收到的数据
- def start(): Unit = synchronized {
- eventActor = ssc.env.actorSystem.actorOf(Props(new Actor {
- def receive = {
- case event: JobGeneratorEvent => processEvent(event)
- }
- }), "JobGenerator")
- if (ssc.isCheckpointPresent) {
- restart()
- } else {
- startFirstTime()
- }
- }
复制代码
1、启动一个actor处理JobGeneratorEvent事件。
2、如果是已经有CheckPoint了,就接着上次的记录进行处理,否则就是第一次启动。
我们先看startFirstTime吧,CheckPoint以后再说吧,有点儿小复杂。
- private def startFirstTime() {
- val startTime = new Time(timer.getStartTime())
- graph.start(startTime - graph.batchDuration)
- timer.start(startTime.milliseconds)
- }
复制代码
- private val timer = new RecurringTimer(clock, ssc.graph.batchDuration.milliseconds,
- longTime => eventActor ! GenerateJobs(new Time(longTime)), "JobGenerator")
复制代码
- private def processEvent(event: JobGeneratorEvent) {
- event match {
- case GenerateJobs(time) => generateJobs(time)
- case ClearMetadata(time) => clearMetadata(time)
- case DoCheckpoint(time) => doCheckpoint(time)
- case ClearCheckpointData(time) => clearCheckpointData(time)
- }
- }
复制代码
下面是generateJobs方法。
- private def generateJobs(time: Time) {
- SparkEnv.set(ssc.env)
- Try(graph.generateJobs(time)) match {
- case Success(jobs) =>
- val receivedBlockInfo = graph.getReceiverInputStreams.map { stream =>
- val streamId = stream.id
- val receivedBlockInfo = stream.getReceivedBlockInfo(time)
- (streamId, receivedBlockInfo)
- }.toMap
- jobScheduler.submitJobSet(JobSet(time, jobs, receivedBlockInfo))
- case Failure(e) =>
- jobScheduler.reportError("Error generating jobs for time " + time, e)
- }
- eventActor ! DoCheckpoint(time)
- }
复制代码
- def generateJobs(time: Time): Seq[Job] = {
- val jobs = this.synchronized {
- outputStreams.flatMap(outputStream => outputStream.generateJob(time))
- }
- jobs
- }
复制代码
outputStreams在这个例子里面是print这个方法里面添加的,这个在前面说了,我们继续看DStream的generateJob。
- private[streaming] def generateJob(time: Time): Option[Job] = {
- getOrCompute(time) match {
- case Some(rdd) => {
- val jobFunc = () => {
- val emptyFunc = { (iterator: Iterator[T]) => {} }
- context.sparkContext.runJob(rdd, emptyFunc)
- }
- Some(new Job(time, jobFunc))
- }
- case None => None
- }
- }
复制代码
- override def generateJob(time: Time): Option[Job] = {
- parent.getOrCompute(time) match {
- case Some(rdd) =>
- val jobFunc = () => {
- foreachFunc(rdd, time)
- }
- Some(new Job(time, jobFunc))
- case None => None
- }
- }
复制代码
- private[streaming] def getOrCompute(time: Time): Option[RDD[T]] = {
- // If this DStream was not initialized (i.e., zeroTime not set), then do it
- // If RDD was already generated, then retrieve it from HashMap
- generatedRDDs.get(time) match {
- // 这个RDD已经被生成过了,直接用就是了
- case Some(oldRDD) => Some(oldRDD)
- // 还没生成过,就调用compte函数生成一个
- case None => {
- if (isTimeValid(time)) {
- compute(time) match {
- case Some(newRDD) =>
- // 设置保存的级别
- if (storageLevel != StorageLevel.NONE) {
- newRDD.persist(storageLevel)
- }
- // 如果现在需要,就做CheckPoint
- if (checkpointDuration != null && (time - zeroTime).isMultipleOf(checkpointDuration)) {
- newRDD.checkpoint()
- }
- // 添加到generatedRDDs里面去,可以再次利用
- generatedRDDs.put(time, newRDD)
- Some(newRDD)
- case None =>
- None
- }
- } else {
- None
- }
- }
- }
- }
复制代码
从上面的方法可以看出来它是通过每个DStream自己实现的compute函数得出来的RDD。我们找到SocketInputDStream,没有compute函数,在父类ReceiverInputDStream里面找到了。
- override def compute(validTime: Time): Option[RDD[T]] = {
- // 如果出现了时间比startTime早的话,就返回一个空的RDD,因为这个很可能是master挂了之后的错误恢复
- if (validTime >= graph.startTime) {
- val blockInfo = ssc.scheduler.receiverTracker.getReceivedBlockInfo(id)
- receivedBlockInfo(validTime) = blockInfo
- val blockIds = blockInfo.map(_.blockId.asInstanceOf[BlockId])
- Some(new BlockRDD[T](ssc.sc, blockIds))
- } else {
- Some(new BlockRDD[T](ssc.sc, Array[BlockId]()))
- }
- }
复制代码
- jobScheduler.submitJobSet(JobSet(time, jobs, receivedBlockInfo))
复制代码
- def submitJobSet(jobSet: JobSet) {
- if (jobSet.jobs.isEmpty) {
- } else {
- jobSets.put(jobSet.time, jobSet)
- jobSet.jobs.foreach(job => jobExecutor.execute(new JobHandler(job)))
- }
- }
复制代码
遍历jobSet里面的所有jobs,通过jobExecutor这个线程池提交。我们看一下JobHandler就知道了。
- private class JobHandler(job: Job) extends Runnable {
- def run() {
- eventActor ! JobStarted(job)
- job.run()
- eventActor ! JobCompleted(job)
- }
- }
复制代码
- def run() {
- result = Try(func())
- }
复制代码
- 顶
- 0
- 踩
Spark Streaming实例的更多相关文章
- Spark源码系列(八)Spark Streaming实例分析
这一章要讲Spark Streaming,讲之前首先回顾下它的用法,具体用法请参照<Spark Streaming编程指南>. Example代码分析 val ssc = )); // 获 ...
- spark streaming 实例
spark-streaming读hdfs,统计文件中单词数量,并写入mysql package com.yeliang; import java.sql.Connection; import java ...
- Spark Streaming 入门指南
这篇博客帮你开始使用Apache Spark Streaming和HBase.Spark Streaming是核心Spark API的一个扩展,它能够处理连续数据流. Spark Streaming是 ...
- Spark Streaming 结合FlumeNG使用实例
SparkStreaming是一个对实时数据流进行高通量.容错处理的流式处理系统,可以对多种数据源(如Kdfka.Flume.Twitter.Zero和TCP 套接字)进行类似map.reduce.j ...
- Spark Streaming之dataset实例
Spark Streaming是核心Spark API的扩展,可实现实时数据流的可扩展,高吞吐量,容错流处理. bin/spark-submit --class Streaming /home/wx/ ...
- Storm介绍及与Spark Streaming对比
Storm介绍 Storm是由Twitter开源的分布式.高容错的实时处理系统,它的出现令持续不断的流计算变得容易,弥补了Hadoop批处理所不能满足的实时要求.Storm常用于在实时分析.在线机器学 ...
- Spark Streaming源码解读之数据清理内幕彻底解密
本期内容 : Spark Streaming数据清理原理和现象 Spark Streaming数据清理代码解析 Spark Streaming一直在运行的,在计算的过程中会不断的产生RDD ,如每秒钟 ...
- Spark入门实战系列--7.Spark Streaming(上)--实时流计算Spark Streaming原理介绍
[注]该系列文章以及使用到安装包/测试数据 可以在<倾情大奉送--Spark入门实战系列>获取 .Spark Streaming简介 1.1 概述 Spark Streaming 是Spa ...
- Spark入门实战系列--7.Spark Streaming(下)--实时流计算Spark Streaming实战
[注]该系列文章以及使用到安装包/测试数据 可以在<倾情大奉送--Spark入门实战系列>获取 .实例演示 1.1 流数据模拟器 1.1.1 流数据说明 在实例演示中模拟实际情况,需要源源 ...
随机推荐
- 如何在 ubuntu linux 一行中执行多条指令
cd /my_folder rm *.jar svn co path to repo mvn compile package install 使用&& 运算符连接指令 cd /my_f ...
- IIS https绑定主机头方法 [可实现禁止直接通过IP访问及“IIS中绑定多个https(但需要多个IP地址)”]
步骤: 打开:%systemroot%\SYSTEM32\inetsrv\config\ 打开:applicationHost.config 查找:<binding protocol=" ...
- April 9 2017 Week 15 Sunday
In the evening one may praise the day. 入夜方能赞美白昼. I think that could be understand in different ways, ...
- MySQL入门很简单: 3 操作数据库
登陆:mysq -u root -p 0409 1). 创建, 删除数据库 SHOW DATABASES; 显示已经存在的数据率 CREATE DATABASES 数据库名: 创建数据库 DROP D ...
- Xapian简明教程(未完成)
第一章 简介 1.1 简介 Xapian是一个开源的搜索引擎库,它可以让开发者自定义的开发一些高级的的索引和查找因素应用在他们的应用中. 通过阅读这篇文档,希望可以帮助你创建第一个你的索引数据库和了解 ...
- IOS Git源代码管理工具
.新建一个“本地仓库” $ git init .配置仓库 >告诉git你是谁 git config user.name lnj >告诉git怎么联系你 git config user.em ...
- POJ-1990 MooFest---两个树状数组
题目链接: https://vjudge.net/problem/POJ-1990 题目大意: 一群牛参加完牛的节日后都有了不同程度的耳聋,第i头牛听见别人的讲话,别人的音量必须大于v[i],当两头牛 ...
- sqlserver 使用小技巧总结
1. 在数据库 查询中 在表名后面 加 英文字母,可以方便 查询 select a.name ,b.hobbynamefrom student as a, hobby as bwhere a ...
- python spark wingide
- using namespace std 是什么意思?
摘录CSDN上面大牛的回答简要意思就是使用标准库,想知道更清楚的继续读下面的. using namespace std 意思: using 和namespace都是C++的关键词. ...