Spark Streaming 简介

流计算简介

数据分为:静态数据和流数据
对应两种计算模式:批量计算和实时计算

批量计算以“静态数据”为对象,可以在很充裕的时间内对海量数据进行批量处理
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的核心概念:

  1. Broker
    Kafka集群包含一个或多个服务器,这种服务器被称为broker
  2. Topic
    每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic。
    (物理上不同Topic的消息分开存储,逻辑上一个Topic的消息虽然保存于一个或多个broker上,
    但用户只需指定消息的Topic即可生产或消费数据而不必关心数据存于何处)
  3. Partition
    Partition是物理上的概念,每个Topic包含一个或多个Partition.
  4. Producer
    负责发布消息到Kafka broker
  5. Consumer
    消息消费者,向Kafka broker读取消息的客户端。
  6. 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

你可能感兴趣的:(Spark Streaming 简介)