Spark Streaming高级应用

本文详细介绍了SparkStreaming如何与Flume和Kafka集成,包括Push和Pull两种方式对接Flume,以及SparkStreaming直接消费Kafka数据。Push方式中,Flume向Spark推送数据,而Pull方式则是Spark从Flume拉取数据。对于Kafka,Spark采用Direct方式,Executor直接读取数据参与计算。此外,还提到了SparkStreaming中的状态处理,如updateStateByKey。

Spark Streaming

StreamingContext流处理的入口

import org.apache.spark._
import org.apache.spark.streaming._
import org.apache.spark.streaming.StreamingContext._
val conf=new SparkConf().setMaster("local[2]").setAppName("streaming demo")
val ssc=new StreamingContext(conf,Seconds(8)) 
Seconds(8) 为批处理间隔时间

在这里插入图片描述

内建流式数据源

$nc -lk 9999  //数据服务器   ssc为StreamingContext对象
#Socket  
val lines = ssc.socketTextStream("single", 9999,StorageLevel.MEMORY_AND_DISK_SER_2)

#文件系统
val lines = ssc.textFileStream("hdfs://192.168.37.200:9000/kgc")

#Flume Sink
val ds = FlumeUtils.createPollingStream(streamCtx, [sink hostname], [sink port]);

#Kafka Consumer
val ds = KafkaUtils.createStream(streamCtx, zooKeeper, consumerGrp, topicMap);

Spark Streaming 集成 Flume

Spark Streaming 通过 PushPull 两种方式对接 Flume 数据源。Push 方式属于推送(由 Flume 向 Spark 推送)而 Pull 属于拉取(Spark 拉取 Flume 的输出)。Push 和 Pull 两者的差别主要体现在Flume Sink 的不同,而 Flume Source 与 Channel 不会受影响

//首先需要额外依赖下面组件:
<dependency>
 <groupId>org.apache.spark</groupId>
 <artifactId>spark-streaming-flume_2.11</artifactId>
 <version>2.4.4</version>
</dependency>

Push 方式

1、Push 方式在 Spark 端实现并打包

import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.flume.{FlumeUtils, SparkFlumeEvent}
import org.apache.spark.streaming.{Seconds, StreamingContext}

/**
  * 第一步: 编写Flume程序, 配置Avro sink
  * 第二步: pom中导入依赖包
  * 第三步: 编写Spark程序, 使用 FlumeUtils.createStream 配置数据源
  * 第四步: 打成jar包, 上传到集群, 使用spark-submit提交
  * 第五步: 启动Flume程序
  */
object Push {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName(this.getClass.getName).setMaster("local[2]")
    val ssc = new StreamingContext(conf,Seconds(5))
    // 1.加载数据源
    val flumeStream: ReceiverInputDStream[SparkFlumeEvent] = FlumeUtils.createStream(ssc,"single",9999)
    val lines: DStream[String] = flumeStream.map(x=>new String(x.event.getBody.array()).trim)
    // 2.对数据进行处理[WordCount]
    val result: DStream[(String, Int)] = lines.flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_)
    // 3.数据写出[print]
    result.print()
    // 4.启动程序
    ssc.start()
    ssc.awaitTermination()
  }
}

2、编写Flume配置文件avro.sink.conf,Flume 架构为:netcat->memory->avro

agent.sources = s1
agent.channels = c1
agent.sinks = sk1

agent.sources.s1.type = netcat
agent.sources.s1.bind = localhost
agent.sources.s1.port = 5678
agent.sources.s1.channels = c1

agent.sinks.sk1.type = avro
agent.sinks.sk1.hostname =single
agent.sinks.sk1.port = 9999
agent.sinks.sk1.channel = c1

agent.channels.c1.type = memory
agent.channels.c1.capacity = 1000

3、先启动Spark应用程序

spark-submit --class Spark_Streaming.Push SparkLearn-1.0.jar
//Spark_Streaming.Push  包名+类名
//SparkLearn-1.0.jar jar包名

4、再启动 Flume Agent

bin/flume-ng agent --name agent -f /root/avro.sink.conf -Dflume.root.logger=INFO,console

5.启动 nc 或者 Telnet 连接 Socket,向 Flume nectcat Source 发送数据,并在
Spark 流应用程序中观察结果

Pull 方式

当采用 Pull 方式时,Flume 架构为:netcat->memory->SparkSink。Sink 全类名为 org.apache.spark.streaming.flume.sink.SparkSink,在Flume 的配置文件中添加。还需要将以下组件放在$FLUME_HOME/lib 下,若存在相同jar包,可将之前的删除或者修改名称,避免jar包冲突
在这里插入图片描述

1.编写 Pull 方式获取 Input DStream 代码并打包

object Pull {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName(this.getClass.getName).setMaster("local[4]")
    val ssc = new StreamingContext(conf,Seconds(5))
    val flumeStream=FlumeUtils.createPollingStream(ssc,"single",9999,StorageLevel.MEMORY_ONLY_SER_2)
    val lines: DStream[String] = flumeStream.map(x=>new String(x.event.getBody.array()).trim)
    // 2.对数据进行处理[WordCount]
    val result: DStream[(String, Int)] = lines.flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_)
    // 3.数据写出[print]
    result.print()
    // 4.启动程序
    ssc.start()
    ssc.awaitTermination()
  }
}

2.编写 Flume 配置文件sink_spark-pull.conf

agent.sources = s1
agent.channels = c1
agent.sinks = sk1

agent.sources.s1.type = netcat
agent.sources.s1.bind = localhost
agent.sources.s1.port = 5678
agent.sources.s1.channels = c1

agent.sinks.sk1.type = org.apache.spark.streaming.flume.sink.SparkSink
agent.sinks.sk1.hostname =single
agent.sinks.sk1.port = 9999
agent.sinks.sk1.channel = c1

agent.channels.c1.type = memory
agent.channels.c1.capacity = 1000

3.先启动 Flume Agent

bin/flume-ng agent --name agent -f /root/sink_spark-pull.conf -Dflume.root.logger=INFO,console

4.再启动 Spark 流应用程序

spark-submit --class Spark_Streaming.Pull SparkLearn-2.0.jar

5.启动 nc 或者 Telnet 连接 Socket,向 Flume nectcat Source 发送数据,并在
Spark 流应用程序中观察结果。

两种方式对比如下:

  1. 对第三方 jar 的依赖:Pull 方式时 Flume 需要调用 Spark 实现的 Sink,所以多依赖一个 spark-streaming-flume-sink,Flume 与 Spark 两边同时依赖。
  2. 启动顺序:Push 方式时 Flume 虽然不依赖 Spark,但 avro sink 需要一个 avro
    服务,这个服务应该先启动。所以 Spark 应先启动。而 Pull 则与之相反。

Spark Streaming 集成 Kafka

Direct 方式采用 Kafka 简单的 consumer api 方式来读取数据,此种方式不再需要专门 Receiver 来持续不断读取数据。当 batch 任务触发时,由 Executor 读取数据,并参与到其他 Executor 的数据计算过程中去。driver 来决定读取多少 offsets,并将 offsets 交由 checkpoints 来维护。将触发下次 batch 任务,再由 Executor 读取 Kafka 数据并计算。

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

object kafka_spark {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName(this.getClass.getName).setMaster("local[4]").set("spark.serializer","org.apache.spark.serializer.KryoSerializer")
    val ssc = new StreamingContext(conf, Seconds(5))
	ssc.checkpoint(".")
    val kafkaParams = Map(
          ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG->"192.168.37.210:9092",
          ConsumerConfig.GROUP_ID_CONFIG -> "test1",
          ConsumerConfig.MAX_POLL_RECORDS_CONFIG->"500",
          ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG->classOf[StringDeserializer],
          ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG->classOf[StringDeserializer],
          ConsumerConfig.AUTO_OFFSET_RESET_CONFIG->"earliest"
        )
    //LocationStrategies.PreferConsistent :每个 Spark Executor作为一个Consumer,Executor与Kafka Partition一对一 。大多数情况下使用,主要是为了均匀分布。
    //ConsumerStrategies.Subscribe:允许订阅固定的主题集合
    val messages = KafkaUtils.createDirectStream[String, String](ssc,LocationStrategies.PreferConsistent,
      ConsumerStrategies.Subscribe[String, String](Set("test"),kafkaParams))
    messages.map(_.value()).flatMap(_.split(" ")).map(word => (word, 1))
      .reduceByKey(_+_).print()
    ssc.start()
    ssc.awaitTermination()
  }
}

Spark Streaming处理带状态的数据

DStream转换操作包括无状态转换和有状态转换,updateStateByKey属于有状态转换,可以跟踪状态的变化

object Spark_Streaming {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local[2]").setAppName("NetworkWordCount")
    val ssc = new StreamingContext(sparkConf, Seconds(5))
    def updateFunction(currentValues: Seq[Int], preValues: Option[Int]): Option[Int] = {
      val curr = currentValues.sum
      val pre = preValues.getOrElse(0)
      Some(curr + pre)
    }
    ssc.checkpoint(".")
    val lines = ssc.socketTextStream("single", 9999)
    val result = lines.flatMap(_.split(" ")).map((_, 1))
    val state = result.updateStateByKey(updateFunction)
    state.print()
    ssc.start()
    ssc.awaitTermination()
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值