Spark Streaming介绍及概括

目录

一、Spark Streaming概述

1、概述

2、什么是DStream

3、Spark与Storm对比

二、Spark Streaming整体架构

三、初始化Spark Streaming

1、Spark Streaming入口

四、运行Spark Streaming

1、IDEA编写WordCount代码

五、Spark Streaming的输入

1、基本数据源

2、高级数据源


一、Spark Streaming概述

1、概述

        Spark Streaming类似于Apache Storm,用于流式数据的处理。根据其官方文档介绍,Spark Streaming有高吞吐量和容错能力强等特点,而且Spark Streaming易用、容错、易整合到Spark体系 。Spark Streaming支持的数据输入源很多,例如:Kafka、Flume、Twitter、ZeroMQ和简单的TCP套接字等等。数据输入后可以用Spark的高度抽象原语如:map、reduce、join、window等进行运算。而结果也能保存在很多地方,如HDFS,数据库等。另外Spark Streaming也能和MLlib(机器学习)以及Graphx完美融合。

                

               

2、什么是DStream

        Spark Streaming使用离散化流(discretized stream)作为抽象表示,叫作DStreamDStream 是随时间推移而收到的数据的序列。在内部,每个时间区间收到的数据都作为 RDD 存在,而 DStream 是由这些 RDD 所组成的序列(因此 得名“离散化”)。

        

源码:

            

        DStream 可以从各种输入源创建,比如 Flume、Kafka 或者 HDFS。创建出来的DStream 支持两种操作,一种是转化操作(transformation),会生成一个新的DStream,另一种是输出操作(output operation),可以把数据写入外部系统中。DStream 提供了许多与 RDD 所支持的操作相类似的操作支持,还增加了与时间相关的新操作,比如滑动窗口。

 

3、Spark与Storm对比

        

 

二、Spark Streaming整体架构

        

        接收器是一直运行的,为了防止数据丢失,会将数据备份到另一工作节点上,通过Spark Streaming写的驱动程序来设置调度批次的时间间隔,假设设置为1秒,那Spark Streaming就会处理接收1秒内接收的所有数据,1秒调度一次,微批次的概念。实际上就是处理RDD的概念。

三、初始化Spark Streaming

1、Spark Streaming入口

第一种:可以通过ssc.sparkContext 来访问SparkContext(Seconds(1):设置处理1秒的数据)

    val conf = new SparkConf().setAppName(appName).setMaster(master)

    val ssc = new StreamingContext(conf, Seconds(1))

 

第二种:通过已经存在的SparkContext来创建StreamingContext(Seconds(1):设置处理1秒的数据)

    val sc = ...      

    val ssc = new StreamingContext(sc, Seconds(1))

初始化完Context之后:

    1) 定义消息输入源来创建DStreams. 

    2) 定义DStreams的转化操作和输出操作。 

    3) 通过 streamingContext.start()来启动消息采集和处理. 

    4) 等待程序终止,可以通过

            streamingContext.awaitTermination()来设置 

    5) 通过streamingContext.stop()来手动终止处理程序。

四、运行Spark Streaming

1、IDEA编写WordCount代码

import org.apache.spark.SparkConf

import org.apache.spark.streaming.{Seconds, StreamingContext}

object WorldCount {

  def main(args: Array[String]) {

    // 需要新建一个sparkConf变量,来提供Spark的配置

    val conf = new SparkConf().setMaster("local[2]").setAppName("NetworkWordCount")

    // 新建一个StreamingContext入口

    val ssc = new StreamingContext(conf, Seconds(1))

    // master01机器上的9999端口不断的获取输入的文本数据

    val lines = ssc.socketTextStream("master01", 9999)

    // 将每一行文本通过空格分隔成多个单词

    val words = lines.flatMap(_.split(" "))

    // 每个单词转换为一个元祖

    val pairs = words.map(word => (word, 1))

    //根据单词来统计相同单词的数量

    val wordCounts = pairs.reduceByKey(_ + _)

    wordCounts.print()        // 打印结果

    ssc.start()             // 启动流式处理程序

    ssc.awaitTermination()  // 等待停止信号

  }

}

2、通过Netcat发送数据:

    先打开9999端口

    

3、按照Spark Core中的方式进行打包,并将程序上传到Spark机器上。并运行:(这是无状态转换)

    bin/spark-submit --class com.atguigu.streaming.WorldCount ~/wordcount-jar-with-dependencies.jar

      

    在9999端口打开的终端窗口输入信息后Enter:

        

    即可在程序运行的中断窗口接收到信息:

        

 

五、Spark Streaming的输入

1、基本数据源

1-1、文件件数据源(使用不多,一般测试使用)

    文件数据流:能够读取所有HDFS API兼容的文件系统文件,通过fileStream方法进行读取

        

    Spark Streaming 将会监控 dataDirectory 目录并不断处理移动进来的文件,记住目前不支持嵌套目录。

        1) 文件需要有相同的数据格式 

        2) 文件进入 dataDirectory的方式需要通过移动或者重命名来实现。 

        3) 一旦文件移动进目录,则不能再修改,即便修改了也不会读取新数据。

    如果文件比较简单,则可以使用 streamingContext.textFileStream(dataDirectoryPath)方法来读取文件。文件流不需要接收器,不需要单独分配CPU核。

        

HDFS读取实例:

提前需在HDFS上建好目录

代码:

    scala> import org.apache.spark.streaming._

    import org.apache.spark.streaming._

 

    scala> val ssc = new StreamingContext(sc, Seconds(1))

    ssc: org.apache.spark.streaming.StreamingContext = org.apache.spark.streaming.StreamingContext@4027edeb

 

    scala> val lines = ssc.textFileStream("hdfs://master01:9000/data/")

    lines: org.apache.spark.streaming.dstream.DStream[String] =                       org.apache.spark.streaming.dstream.MappedDStream@61d9dd15

 

    scala> val words = lines.flatMap(_.split(" "))

    words:org.apache.spark.streaming.dstream.DStream[String]=

    org.apache.spark.streaming.dstream.FlatMappedDStream@1e084a26

    scala> val wordCounts = words.map(x => (x, 1)).reduceByKey(_ + _)

    wordCounts: org.apache.spark.streaming.dstream.DStream[(String, Int)] =                   

    org.apache.spark.streaming.dstream.ShuffledDStream@8947a4b

 

    scala> wordCounts.print()

    scala> ssc.start()

        -------------------------------------------

        Time: 1504665716000 ms

        -------------------------------------------

        -------------------------------------------

        Time: 1504665717000 ms

        -------------------------------------------

上传文件到HDFS:     

    

获取结算结果:

    

    

总结:

1、Spark Streaming通过streamingContext.fileStream[KeyClass, ValueClass, InputFormatClass](dataDirectory) 这个方法提供了对目录下文件数据源的支持。

2、如果你的文件是比较简单的文本文件,你可以使用 streamingContext.textFileStream(dataDirectory)  来代替。

3、文件数据源目前不支持嵌套目录:

      1、 文件需要有相同的数据格式

      2、文件进入 dataDirectory的方式需要通过移动或者重命名来实现

      3、一旦文件移动进目录,则不能再修改,即便修改了也不会读取新数据。

1-2、自定义数据源(*****)

    通过继承Receiver(自定义Receiver,并实现onStart、onStop方法来自定义数据源采集。

代码:

import java.io.{BufferedReader, InputStreamReader}

import java.net.Socket

import java.nio.charset.StandardCharsets

import org.apache.spark.SparkConf

import org.apache.spark.storage.StorageLevel

import org.apache.spark.streaming.{Seconds, StreamingContext}

import org.apache.spark.streaming.receiver.Receiver

 

class CustomReceiver (host: String, port: Int) extends Receiver[String](StorageLevel.MEMORY_AND_DISK_2) {

    //Receiver启动的时候需要调用的方法

    override def onStart(): Unit = {

    //定义一个新的线程去运行我的receiver方法

        new Thread("Socket Receiver") {

        override def run() { receive() }

        }.start()

    }

 

    //Receiver停止的时候需要调用的方法

    override def onStop(): Unit = {

        // There is nothing much to do as the thread calling receive()

        // is designed to stop by itself if isStopped() returns false

    }

 

    //我的receiver方法

    private def receive() {

        var socket: Socket = null

        var userInput: String = null

        try {

            //新建一个socket连接

            socket = new Socket(host, port)

            //获取socket的输入

            val reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))

            //获取第一条输入

            userInput = reader.readLine()

            //如果你的Receiver没有停止并且userInput不是null

            while(!isStopped && userInput != null) {

                //传送出来

                store(userInput)

                //继续读取下一行

                userInput = reader.readLine()

            }

            //关闭资源

            reader.close()

            socket.close()

 

            // Restart in an attempt to connect again when server is active again

            restart("Trying to connect again")

        } catch {

            case e: java.net.ConnectException =>

            // restart if could not connect to server

            restart("Error connecting to " + host + ":" + port, e)

            case t: Throwable =>

            // restart if there is any other error

            restart("Error receiving data", t)

        }

    }

}

 

object CustomReceiver {

    def main(args: Array[String]) {

        val conf = new SparkConf().setMaster("local[2]").setAppName("NetworkWordCount")

        val ssc = new StreamingContext(conf, Seconds(1))

 

        // Create a DStream that will connect to hostname:port, like localhost:9999

        val lines = ssc.receiverStream(new CustomReceiver("master01", 9999))

 

        // Split each line into words

        val words = lines.flatMap(_.split(" "))

 

        //import org.apache.spark.streaming.StreamingContext._ // not necessary since Spark 1.3

        // Count each word in each batch

        val pairs = words.map(word => (word, 1))

        val wordCounts = pairs.reduceByKey(_ + _)

 

        // Print the first ten elements of each RDD generated in this DStream to the console

        wordCounts.print()

 

        ssc.start() // Start the computation

        ssc.awaitTermination() // Wait for the computation to terminate

        //ssc.stop()

    }

}

启动9999端口发送数据:

    

    

打包运行:

    

端口发送数据后:

    

总结:

自定义Receiver

    1、你需要新建一个Class去继承Receiver,并给Receiver传入一个类型参数,该类型参数是你需要接收的数据的类型。

    2、你需要去复写Receiver的方法: onStart方法(在Receiver启动的时候调用的方法)、onStop方法(在Receiver正常停止的情况下调用的方法)

    3、你可以在程序中通过streamingContext.receiverStream( new CustomeReceiver)来调用你定制化的Receiver。

1-3、RDD队列(了解)(day06==>第03集91:00-99:00)

    测试过程中,可以通过使用streamingContext.queueStream(queueOfRDDs)来创建DStream,每一个推送到这个队列中的RDD,都会作为一个DStream处理。

代码:

import org.apache.spark.SparkConf

import org.apache.spark.rdd.RDD

import org.apache.spark.streaming.{Seconds, StreamingContext}

import scala.collection.mutable

 

object QueueRdd {

    def main(args: Array[String]) {

        val conf = new SparkConf().setMaster("local[2]").setAppName("QueueRdd")

        val ssc = new StreamingContext(conf, Seconds(1))

 

        //创建RDD队列

        val rddQueue = new mutable.SynchronizedQueue[RDD[Int]]()

 

        // 创建QueueInputDStream

        val inputStream = ssc.queueStream(rddQueue)

 

        //处理队列中的RDD数据

        val mappedStream = inputStream.map(x => (x % 10, 1))

        val reducedStream = mappedStream.reduceByKey(_ + _)

 

        //打印结果

        reducedStream.print()

 

        //启动计算

        ssc.start()

 

        // Create and push some RDDs into

        //for循环模拟插入数据

        for (i <- 1 to 30) {

            rddQueue += ssc.sparkContext.makeRDD(1 to 300, 10)

            Thread.sleep(2000)

            //通过程序停止StreamingContext的运行

            //ssc.stop()

        }

        ssc.awaitTermination()

    }

}

 

打包运行:

    

2、高级数据源

2-1、Spark Streaming和Kafka的集成(*****)

下面例子是为了演示SparkStreaming如何从Kafka读取消息,如果通过连接池方法把消息处理完成后再写回Kafka:

     

代码:

kafka Connection Pool程序:

import java.util.Properties

import org.apache.commons.pool2.impl.DefaultPooledObject

import org.apache.commons.pool2.{BasePooledObjectFactory, PooledObject}

import org.apache.kafka.clients.producer.{KafkaProducer, ProducerRecord}

/**

* @param brokerList:Kafka连接地址

* @param producerConfig:生产者配置

* @param defaultTopic:默认写出的队列名

* @param producerKafkaProducer

*/

case class KafkaProducerProxy(brokerList: String,

                                                    producerConfig: Properties = new Properties,

                                                    defaultTopic: Option[String] = None,

                                                    producer: Option[KafkaProducer[String, String]] = None) {

 

    type Key = String

    type Val = String

 

    require(brokerList == null || !brokerList.isEmpty, "Must set broker list")

 

    private val p = producer getOrElse {

        var props:Properties= new Properties();

        props.put("bootstrap.servers", brokerList);

        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

 

        new KafkaProducer[String,String](props)

    }

 

    //把我的消息包装成了ProducerRecord

    private def toMessage(value: Val, key: Option[Key] = None, topic: Option[String] = None): ProducerRecord[Key, Val] = {

        val t = topic.getOrElse(defaultTopic.getOrElse(throw new IllegalArgumentException("Must provide topic or default topic")))

        require(!t.isEmpty, "Topic must not be empty")

        key match {

            case Some(k) => new ProducerRecord(t, k, value)

            case _ => new ProducerRecord(t, value)

        }

    }

    

    def send(key: Key, value: Val, topic: Option[String] = None) {

        //调用KafkaProducersend方法发送消息

        p.send(toMessage(value, Option(key), topic))

    }

 

    def send(value: Val, topic: Option[String]) {

        send(null, value, topic)

    }

 

    def send(value: Val, topic: String) {

        send(null, value, Option(topic))

    }

 

    def send(value: Val) {

        send(null, value, None)

    }

 

    def shutdown(): Unit = p.close()

 

}

 

abstract class KafkaProducerFactory(brokerList: String, config: Properties, topic: Option[String] = None) extends Serializable {

    def newInstance(): KafkaProducerProxy

}

 

class BaseKafkaProducerFactory(brokerList: String,config: Properties = new Properties,defaultTopic: Option[String] = None)

        extends KafkaProducerFactory(brokerList, config, defaultTopic) {

    override def newInstance() = new KafkaProducerProxy(brokerList, config, defaultTopic)

}

 

//继承一个基础的连接池,需要提供池化的对象类型

class PooledKafkaProducerAppFactory(val factory: KafkaProducerFactory)

        extends BasePooledObjectFactory[KafkaProducerProxy] with Serializable {

    //用于池来创建对象

    override def create(): KafkaProducerProxy = factory.newInstance()

    //用于池来包装对象

    override def wrap(obj: KafkaProducerProxy): PooledObject[KafkaProducerProxy] = new DefaultPooledObject(obj)

    //用于池来销毁对象

    override def destroyObject(p: PooledObject[KafkaProducerProxy]): Unit = {

        p.getObject.shutdown()

        super.destroyObject(p)

    }

}

 

KafkaStreaming main:

import org.apache.commons.pool2.impl.{GenericObjectPool, GenericObjectPoolConfig}

import org.apache.kafka.clients.consumer.ConsumerRecord

import org.apache.kafka.common.serialization.StringDeserializer

import org.apache.spark.SparkConf

import org.apache.spark.api.java.function.VoidFunction

import org.apache.spark.rdd.RDD

import org.apache.spark.streaming.kafka010.{ConsumerStrategies, KafkaUtils, LocationStrategies}

import org.apache.spark.streaming.{Seconds, StreamingContext}

 

//单例对象(保证了池只有一个)

object createKafkaProducerPool{

    //用于返回真正的对象池

    def apply(brokerList: String, topic: String): GenericObjectPool[KafkaProducerProxy] = {

        val producerFactory = new BaseKafkaProducerFactory(brokerList, defaultTopic = Option(topic))

        val pooledProducerFactory = new PooledKafkaProducerAppFactory(producerFactory)

        //指定了你的kafka对象池的大小

        val poolConfig = {

            val c = new GenericObjectPoolConfig

            val maxNumProducers = 10

            c.setMaxTotal(maxNumProducers)

            c.setMaxIdle(maxNumProducers)

            c

        }

        //返回一个对象池

        new GenericObjectPool[KafkaProducerProxy](pooledProducerFactory, poolConfig)

    }

}

 

object KafkaStreaming{

    def main(args: Array[String]) {

        //设置sparkConf

        val conf = new SparkConf().setMaster("local[4]").setAppName("NetworkWordCount")

        //新建了streamingContext

        val ssc = new StreamingContext(conf, Seconds(1))

        //kafka地址

        val brobrokers = "172.16.148.150:9092,172.16.148.151:9092,172.16.148.152:9092"

        //kafka的队列名称

        val sourcetopic="source";

        val targettopic="target";

 

        //创建消费者组

        var group="con-consumer-group"

        //Kafka消费者配置(具体应用时再去官网查配置信息)

        val kafkaParam = Map(

            "bootstrap.servers" -> brobrokers,     //用于初始化链接到集群的地址

            "key.deserializer" -> classOf[StringDeserializer],

            "value.deserializer" -> classOf[StringDeserializer],

            //用于标识这个消费者属于哪个消费团体

            "group.id" -> group,

            //如果没有初始化偏移量或者当前的偏移量不存在任何服务器上,可以使用这个配置属性

            //可以使用这个配置,latest自动重置偏移量为最新的偏移量,为了获取最新的消息

            "auto.offset.reset" -> "latest",

            //如果是true,则这个消费者的偏移量会在后台自动提交

            "enable.auto.commit" -> (false: java.lang.Boolean)

        );

 

        //ssc.sparkContext.broadcast(pool)

 

        //创建DStream,返回接收到的输入数据

        var stream=KafkaUtils.createDirectStream[String,String](ssc,                                

                                LocationStrategies.PreferConsistent,ConsumerStrategies.Subscribe[String,String](Array(sourcetopic),kafkaParam))

 

        //每一个stream都是一个ConsumerRecord

        stream.map(s =>("id:" + s.key(),">>>>:"+s.value())).foreachRDD(rdd => {

            //对于RDD的每一个分区执行一个操作

            rdd.foreachPartition(partitionOfRecords => {

                //Kafka连接池

                val pool = createKafkaProducerPool(brobrokers, targettopic)

                //从连接池里面取出了一个Kafka的连接

                val p = pool.borrowObject()

                //发送当前分区里面每一个数据(send

                partitionOfRecords.foreach {message => System.out.println(message._2);p.send(message._2,Option(targettopic))}

                //使用完了需要将kafka还回去

                pool.returnObject(p)

 

            })

        })

        ssc.start()

        ssc.awaitTermination()

    }

}

程序部署:

1、启动zookeeper和kafka(master、slave1、slave2三台机器都启动)

bin/kafka-server-start.sh -daemon ./config/server.properties

2、创建两个topic,一个为source,一个为target

bin/kafka-topics.sh --create --zookeeper 192.168.1.101:2181,192.168.1.102:2181,192.168.1.103:2181,192.168.1.104:2181 --replication-factor 2 --partitions 2 --topic source

bin/kafka-topics.sh --create --zookeeper 192.168.1.101:2181,192.168.1.102:2181,192.168.1.103:2181,192.168.1.104:2181 --replication-factor 2 --partitions 2 --topic targe

master节点下:

    

3、启动kafka console producer写入source topic

bin/kafka-console-producer.sh --broker-list 192.168.1.101:9092,192.168.1.102:9092, 192.168.1.103:9092,

192.168.1.104:9092 --topic source

4、启动kafka console consumer监听target topic(验证是否成功)

bin/kafka-console-consumer.sh --bootstrap-server 192.168.1.101:9092,192.168.1.102:9092, 192.168.1.103:9092,

192.168.1.104:9092 --topic source

验证:

    

    

5、启动kafkaStreaming程序:

[bigdata@master01 ~]$ ./hadoop/spark-2.1.1-bin-hadoop2.7/bin/spark-submit --class com.atguigu.streaming.KafkaStreaming ./kafkastreaming-jar-with-dependencies.jar

打包上传运行:

    

一个端口发送数据:将数据写入到source1

    

接收数据:KafkaStreaming给接收的数据前面加了:" >>>>: "

    

consumer监控target1队列,所以当KafkaStreaming把数据传入target1倍consumer监控到时,consumer就会直接将数据打印出来

    

 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值