流计算简介
数据分为:静态数据和流数据
对应两种计算模式:批量计算和实时计算
批量计算以“静态数据”为对象,可以在很充裕的时间内对海量数据进行批量处理
Hadoop就是典型的批处理模型,由HDFS和HBase存放大量的静态数据,由MapReduce负责对海量数据执行批量计算。
流计算针对的是流数据,必须采用实时计算
流计算秉承一个基本理念,即数据的价值随着时间的流逝而降低
Spark Streaming简介
Spark Streaming可结合批处理和交互查询,适合一些需要对历史数据和实时数据进行结合分析的应用场景。
Spark Streaming可整合多种输入数据源,如Kafka、Flume、HDFS、TCP socket。经处理后的数据可存储至 HDFS、数据库,等。
基本原理:将实时输入数据流以时间片(秒级)为单位进行拆分,然后经Spark引擎以类似批处理的方式处理每个时间片数据
Spark Streaming 无法实现毫秒级的流计算,因为其将流数据按批处理窗口大小(通常在0.5~2秒之间)分解为一系列批处理作业,在这个过程中,会产生多个Spark 作业,且每一段数据的处理都会经过Spark DAG图分解、任务调度过程,因此,无法实现毫秒级相应。
DStream 操作简述
Spark Streaming工作原理
在 Spark 中,一个应用(Application)是由一个任务控制节点(Driver)和若干个作业(job)组成。
一个作业由多个阶段(Stage)组成,一个阶段又由多个任务(Task)组成。
当执行一个应用时,任务控制节点会向集群管理器(Cluster Manager)申请资源,启动Executor,并向 Executor 发送应用程序代码和文件,然后在 Executor 上执行任务。
在 Spark Streaming 中,会有一个组件 Receiver,作为一个长期运行的task 跑在 Executor上,一个Receiver 负责一个 Input DStream(比如文件流、kafka中读取的输入流、套接字等)。
Spark Streaming 通过 input DStream 与外部数据源进行连接,读取相关数据。
Spark Streaming 程序基本步骤
1、首先需要确定一个输入源,比如文件等,即创建输入DStream 来定义输入源
2、确定输入源的转换 和 输出操作,来定义流计算
3、用 streamingContext.start() 来开始接收数据
4、用 streamingContext.awaitTermination() 来等待处理结束
5、用 streamingContext.stop() 来手动结束流计算进程
创建StreamingContext对象
-
在 Spark shell 中:
import org.apache.spark.streaming._ val ssc = new StreamingContext(sc, Seconds(1)) //每隔1秒自动执行一次流计算
-
在程序中:
import org.apache.spark._ import org.apache.spark.streaming._ //”local[2]’ 表示运行在本地模式下,并且启动2个工作线程 val conf = new SparkConf().setAppName("TestDStream").setMaster("local[2]") val ssc = new StreamingContext(conf, Seconds(1))
基本输入源:
文件流:
首先需要创建一个文件,下面是在 spark shell 中的代码
scala> import org.apache.spark.streaming._
scala> val ssc = new StreamingContext(sc, Seconds(20)) //每隔20秒执行一次流计算
scala> val lines = ssc.textFileStream("file:///usr/local/spark/mycode/streaming/logfile")
scala> val words = lines.flatMap(_.split(" "))
scala> val wordCounts = words.map(x => (x, 1)).reduceByKey(_ + _)
scala> wordCounts.print()
scala> ssc.start()
scala> ssc.awaitTermination()
调用 start 之后,Spark Streaming 就会每隔20秒监听一次,不会输出目录中已存在的文件
而是会处理 输出 start 之后,新增的文件内容。
在程序中的处理方式要通过 sbt 打包成jar,并发送到 spark 中运行,不再详述。
套接字流:
套接字流代码较多,不在这里记录,详情参考:http://dblab.xmu.edu.cn/blog/1387-2/
主要就是创建一个DataSourceSocket类:创建一个套接字,并读取一个文件,创建一个输出流,把数据发送出去
然后创建一个NetworkWordCount类:主要用来接收 socket,并对数据进行转换处理,然后调用 start,等。
RDD 队列流
创建一个 RDD 队列(1),把这个RDD队列作为输入流(2),对其进行监听,每隔一段时间进行一次流计算(3)。
在这段时间中,RDD队列中不停有新加入的RDD(4)
(1)
val rddQueue = new scala.collection.mutable.SynchronizedQueue[RDD[Int]]()
(2)
val queueStream = ssc.queueStream(rddQueue)
(3)
val mappedStream = queueStream.map(r => (r % 10, 1))
val reducedStream = mappedStream.reduceByKey(_ + _)
reducedStream.print()
ssc.start()
(4)
for (i <- 1 to 10) { //每隔一秒创建一个RDD,并添加到队列中
rddQueue += ssc.sparkContext.makeRDD(1 to 100,2)
Thread.sleep(1000)
}
20秒一次的输出结果:
(4,10)
(0,10)
(6,10)
(8,10)
(2,10)
(1,10)
(3,10)
(7,10)
(9,10)
(5,10)
高级输入源
kafka,是非常流行的日志采集系统,可以作为DStream的高级数据源。(kafka的相关东东还要好好了解下,下面说的很简单)
kafka的核心概念:
- Broker
Kafka集群包含一个或多个服务器,这种服务器被称为broker - Topic
每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic。
(物理上不同Topic的消息分开存储,逻辑上一个Topic的消息虽然保存于一个或多个broker上,
但用户只需指定消息的Topic即可生产或消费数据而不必关心数据存于何处) - Partition
Partition是物理上的概念,每个Topic包含一个或多个Partition. - Producer
负责发布消息到Kafka broker - Consumer
消息消费者,向Kafka broker读取消息的客户端。 - Consumer Group
每个Consumer属于一个特定的Consumer Group(可为每个Consumer指定group name,若不指定group name则属于默认的group)
kafka的安装配置,简单实例:http://dblab.xmu.edu.cn/blog/1096-2/
1、安装kafka
2、启动 zookeeper,启动 server
3、启动 topic,配置 topic名称,配置 partitions 数量等
4、启动一个 producer 生产数据
5、启动一个 consumer 来消费数据
kafka作为 Spark DStream 的输入源,并对流数据进行处理:
1、首先下载 spark需要的 kafka 的jar包,放在spark 的jars目录中
2、编写程序,创建一个producer类,生产一些数据(可以通过 ProducerRecord 对数据进行封装),
并调用 producer 的 send方法
3、创建一个消费者类,用来接收和处理数据
代码实现:
KafkaWordProducer.scala:
产生一系列字符串的程序,会产生随机的整数序列,
每个整数被当做一个单词,提供给KafkaWordCount程序去进行词频统计。
以下是 main 函数中部分代码:
//args为外部传入的参数,brokers为服务器地址,topic为topic的名称,
//messagesPerSec为每秒发送几条消息,wordsPerMessage 为一条消息包含几个单词
val Array(brokers, topic, messagesPerSec, wordsPerMessage) = args
//创建一个 HashMap,把brokers、key、value 放进去
val serializer = "org.apache.kafka.common.serialization.StringSerializer"
val props = new HashMap[String, Object]()
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, brokers)
//kafka消息key序列化类 若传入key的值,则根据该key的值进行hash散列计算出在哪个partition上
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, serializer)
//kafka消息序列化类 即将传入对象序列化为字节数组
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, serializer)
//创建一个 producer
val producer = new KafkaProducer[String, String](props)
// Send some messages
while(true) {
(1 to messagesPerSec.toInt).foreach { messages =>
//随机生成一个在0到10之间的int值,转为string
val str = (1 to wordsPerMessage.toInt).map(scala.util.Random.nextInt(10).toString).mkString(" ")
val message = new ProducerRecord[String, String](topic, null, str)
producer.send(message)
}
Thread.sleep(1000) //每隔1秒发送一次
}
运行 KafkaWordProducer.scala
spark-submit --driver-class-path /zyb/spark/jars/*:/zyb/spark/jars/kafka/* \
--class "org.apache.spark.examples.streaming.KafkaWordProducer" \
/zyb/spark/mycode/kafka/target/scala-2.11/simple-project_2.11-1.0.jar \
localhost:9092 wordsender 3 5
”–driver-class-path /usr/local/spark/jars/:/usr/local/spark/jars/kafka/“
来指定应用程序依赖的相关jar包的路径。
”localhost:9092 wordsender 3 5“ 是提供给KafkaWordProducer程序的4个输入参数
第1个参数 localhost:9092 是Kafka的broker的地址
第2个参数 wordsender 是topic的名称
第3个参数 “3” 表示每秒发送3条消息
第4个参数 “5” 表示,每条消息包含5个单词(实际上就是5个整数)。
KafkaWordCount.scala
是用于单词词频统计,它会把KafkaWordProducer发送过来的单词进行词频统计
部分代码:
val sc = new SparkConf().setAppName("KafkaWordCount").setMaster("local[2]")
val ssc = new StreamingContext(sc,Seconds(10))
val zkQuorum = "localhost:2181" //Zookeeper服务器地址
val group = "1" //topic所在的group,可以设置为自己想要的名称,比如 val group = "test-consumer-group"
val topics = "wordsender" //topics的名称
val numThreads = 1 //每个topic的分区数
val topicMap = topics.split(",").map((_, numThreads.toInt)).toMap
val lineMap = KafkaUtils.createStream(ssc, zkQuorum, group, topicMap)
val lines = lineMap.map(_._2)
val words = lines.flatMap(_.split(" "))
val pair = words.map(x => (x,1))
//这行代码的含义在下一节的窗口转换操作中会有介绍
//大致意思和 reduceByKey 差不多,都是对相同key进行合并
val wordCounts = pair.reduceByKeyAndWindow(_ + _,_ - _,Minutes(2),Seconds(10),2)
wordCounts.print
ssc.start
ssc.awaitTermination
运行KafkaWordCount程序,在这里省略...
关于 kafka的文章可以移到这里:Kafka
DStream转换操作
无状态转换
每个批次的处理不依赖于之前批次的数据
包含 map、flatMap、filter、reduce、reduceByKey 等)。
有状态转换
当前批次的处理需要使用之前批次的数据或者中间结果。
有状态转换包括:基于滑动窗口的转换 和 追踪状态变化的转换(updateStateByKey)。
滑动窗口的转换:
事先设定一个滑动窗口的长度(也就是窗口的持续时间),
并且设定滑动窗口的时间间隔(每隔多长时间执行一次计算),
然后,就可以让窗口按照指定时间间隔在源DStream上滑动,每次窗口停放的位置上,
都会有一部分DStream被框入窗口内,形成一个小段的DStream,
这时,就可以启动对这个小段DStream的计算。
updateStateByKey操作:当我们需要在跨批次之间维护状态时,就必须使用updateStateByKey操作
DStream输出操作
外部系统经常需要使用到Spark DStream处理后的数据,
因此,需要采用输出操作把DStream的数据输出到数据库或者文件系统中。
输出到文件比较简单:
stateDstream.saveAsTextFiles("file:///usr/local/spark/mycode/streaming/dstreamoutput/output.txt")
说一下输出到 MySql
卧槽,内容挺多的,不说了,直接看吧。。。http://dblab.xmu.edu.cn/blog/1121-2/
总结一下就是:先创建一个表,然后把处理后的流数据的每个RDD,进行重新分区,然后连接到 MySql,
然后设置 插入语句,然后把数据插入进去,即可。
下一节重点讲 Spark Streaming 的转换操作:Spark Streaming 的 Transformations