基于Spark Streaming 的流数据处理和分析

一.流介绍

1.流是什么

数据流

  • 数据的流入
  • 数据的处理
  • 数据的流出

随处可见的数据流

  • 电商网站、日志服务器、社交网络和交通监控产生的大量实时数据

流处理

  • 是一种允许用户在接收到的数据后的短时间内快速查询连续数据流和检测条件的技术

2.为什么需要流处理

它能够更快地提供洞察力,通常在毫秒到秒之间
大部分数据的产生过程都是一个永无止境的事件流

  • 要进行批处理,需要存储它,在某个时间停止数据收集,并处理数据
  • 流处理适合时间序列数据和检测模式随时间推移

3.流处理应用场景

  • 股市监控
  • 交通监控
  • 计算机系统与网络监控
  • 监控生产线
  • 供应链优化
  • 入侵、监视和欺诈检测
  • 大多数智能设备应用
  • 上下文感知促销和广告

4.如何进行流处理

常用流处理框架

  • Apache Spark Streaming
  • Apache Flink
  • Apache Storm
  • Confluent

二.Spark Streaming

是基于Spark Core API的扩展,用于流式数据处理

  • 支持多种数据源和多种输出
    在这里插入图片描述

高容错
可扩展
高流量
低延时(Spark 2.3.1 延时1ms,之前100ms)

1.Spark Streaming流数据处理架构

典型架构
在这里插入图片描述

2.Spark Streaming内部工作流程

微批处理:输入->分批处理->结果集

  • 以离散流的形式传入数据(DStream:Discretized Streams)
  • 流被分成微批次(1-10s),每一个微批都是一个RDD
  • 计算过程由Spark engine来完成
    在这里插入图片描述

3.StreamingContext

  • Spark Streaming流处理的入口
  • 2.2版本SparkSession未整合StreamingContext,所以仍需单独创建
1、一个JVM只能有一个StreamingContext启动
2、StreamingContext停止后不能再启动
import org.apache.spark._
import org.apache.spark.streaming._
import org.apache.spark.streaming.StreamingContext._
val conf=new SparkConf().setMaster("local[2]").setAppName("kgc streaming demo")
val ssc=new StreamingContext(conf,Seconds(8)) 
/*
在spark-shell下,会出现如下错误提示:
org.apache.spark.SparkException: Only one SparkContext may be running in this JVM
解决:
方法1、sc.stop    //创建ssc前,停止spark-shell自行启动的SparkContext
方法2、或者通过已有的sc创建ssc:val ssc=new StreamingContext(sc,Seconds(8))
*/

4.Spark Streaming快速入门

  • 单词统计——基于TCPSocket接收文本数据
//按照nc服务器
yum install nc
 //数据服务器。当ssc启动后输入测试数据,观察Spark Streaming处理结果
nc -lk 9999 
import org.apache.spark._
import org.apache.spark.streaming._
import org.apache.spark.streaming.StreamingContext._
val sparkConf = new SparkConf().setMaster("local[2]").setAppName("NetworkWordCount")
val ssc = new StreamingContext(sparkConf, Seconds(1))
val lines = ssc.socketTextStream("localhost", 9999)//指定数据源
val words = lines.flatMap(_.split(" "))
val wordCounts = words.map(x => (x, 1)).reduceByKey(_ + _)
wordCounts.print()
ssc.start()
ssc.awaitTermination()

三.Dstream

离散数据流(Discretized Stream)是Spark Streaming提供的高级别抽象
DStream代表了一系列连续的RDDs

  • 每个RDD都包含一个时间间隔内的数据
  • DStream既是输入的数据流,也是转换处理过的数据流
  • 对DStream的转换操作即是对具体RDD操作

在这里插入图片描述

1.Input DStreams与接收器(Receivers)

Input DStream指从某种流式数据源(Streaming Sources)接收流数据的DStream

  • 内建流式数据源:文件系统、Socket、Kafka、Flume……
  • 每一个Input DStream(file stream除外)都与一个接收器(Receiver)相关联,接收器是从数据源提取数据到内存的专用对象
    在这里插入图片描述
    在这里插入图片描述

2.Dstream创建(内建流式数据源)

文件系统

def textFileStream(directory: String): DStream[String]

Socket

def socketTextStream(hostname: String, port: Int, storageLevel: StorageLevel): ReceiverInputDStream[String]

Flume Sink

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

Kafka Consumer

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

3.DStream支持的转换算子

map,flatMap
filter
count, countByValue
repartition
union, join, cogroup
reduce, reduceByKey
transform
updateStateByKey

import org.apache.spark._
import org.apache.spark.streaming._
import org.apache.spark.streaming.StreamingContext._
//DStream支持的转换算子与RDD类似
val input1 = List((1, true), (2, false), (3, false), (4, true), (5, false))
val input2 = List((1, false), (2, false), (3, true), (4, true), (5, true))

val rdd1 = sc.parallelize(input1)
val rdd2 = sc.parallelize(input2)
val ssc = new StreamingContext(sc, Seconds(3))
import scala.collection.mutable
val ds1 = ssc.queueStream[(Int, Boolean)](mutable.Queue(rdd1))
val ds2 = ssc.queueStream[(Int, Boolean)](mutable.Queue(rdd2))

val ds = ds1.join(ds2)
ds.print()
ssc.start()
ssc.awaitTerminationOrTimeout(5000)
ssc.stop()

转换算子-transform

  • transform操作允许在DStream应用任意RDD-TO-RDD的函数
// RDD 包含垃圾邮件信息
// 从Hadoop接口API创建RDD
val spamRDD = ssc.sparkContext.newAPIHadoopRDD(...)
val cleanedDStream = wordCounts.transform {
    
     rdd =>
	//用垃圾邮件信息连接数据流进行数据清理	rdd.join(spamRDD).filter( /* code... */)
	// 其它操作...
}

DStream输出算子

  • print()
  • saveAsTextFiles(prefix,[suffix])
  • saveAsObjectFiles(prefix,[suffix])
  • saveAsHadoopFiles(prefix,[suffix])
  • foreachRDD(func)
  • 1)接收一个函数,并将该函数作用于DStream每个RDD上
  • 2)函数在Driver节点中执行

输出算子-foreachRDD

  • 错误案例
dstream.foreachRDD {
    
     rdd =>
	val connection = createNewConnection() // 在driver节点执行
	rdd.foreach {
    
     record =>
		connection.send(record) // 在worker节点执行
	}
}
  • 正确案例
dstream.foreachRDD {
    
     rdd =>
	rdd.foreachPartition {
    
     partitionOfRecords =>
		val connection = createNewConnection()
		partitionOfRecords.foreach(record => 	
							connection.send(record))
	}
}

四.Spark Streaming编程实例

1.需求:使用Spark Streaming统计HDFS文件的词频

  • 关键代码
val sparkConf = new SparkConf().setAppName("HdfsWordCount").setMaster("local[2]")
val ssc = new StreamingContext(sparkConf, Seconds(2))

// 创建FileInputDStream去读取文件系统上的数据
val lines = ssc.textFileStream("/data/input")
//使用空格进行分割每行记录的字符串
val words = lines.flatMap(_.split(" "))
//类似于RDD的编程,将每个单词赋值为1,并进行合并计算
val wordCounts = words.map(x => (x, 1)).reduceByKey(_ + _)
wordCounts.print()
ssc.start()
ssc.awaitTermination()

2.需求:使用Spark Streaming处理带状态的数据

需求:计算到目前为止累计词频的个数
分析:DStream转换操作包括无状态转换和有状态转换

  • 无状态转换:每个批次的处理不依赖于之前批次的数据

  • 有状态转换:当前批次的处理需要使用之前批次的数据

  • updateStateByKey属于有状态转换,可以跟踪状态的变化
    实现要点

  • 定义状态:状态数据可以是任意类型

  • 定义状态更新函数:参数为数据流之前的状态和新的数据流数据

  • 关键代码:UpdateStateByKeyDemo.scala

//定义状态更新函数
def updateFunction(currentValues: Seq[Int], preValues: Option[Int]): Option[Int] = {
    
    
    val curr = currentValues.sum
    val pre = preValues.getOrElse(0)
    Some(curr + pre)
}

val sparkConf = new SparkConf().setAppName("StatefulWordCount").setMaster("local[2]")
val ssc = new StreamingContext(sparkConf, Seconds(5))
  //todo 做一个checkpoint存储数据
ssc.checkpoint(".")
val lines = ssc.socketTextStream("localhost", 6789)
val result = lines.flatMap(_.split(" ")).map((_, 1))
val state = result.updateStateByKey(updateFunction)
state.print()
ssc.start()
ssc.awaitTermination()

3.Spark Streaming整合Spark SQL

  • 需求:使用Spark Streaming +Spark SQL完成WordCount
  • 分析:将每个RDD转换为DataFrame
case class Word(word:String)
val sparkConf = new SparkConf().setAppName("NetworkSQLWordCount").setMaster("local[2]")
val ssc = new StreamingContext(sparkConf, Seconds(5))
val spark=SparkSession.builder.config(sparkConf).getOrCreate()
val lines = ssc.socketTextStream("localhost", 6789)
val result = lines.flatMap(_.split(" "))
result.print()

result.foreachRDD(rdd => {
    
    
      if (rdd.count() != 0) {
    
    
        import spark.implicits._
        //将RDD转换成DataFrame
        val df = rdd.map(x => Word(x)).toDF
        df.registerTempTable("tb_word")
        spark.sql("select word, count(*) from tb_word group by word").show
      }})
ssc.start()
ssc.awaitTermination()

4.Spark Streaming整合Flume

  • Flume依赖:
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-streaming-flume_2.11</artifactId>
            <version>${spark.version}</version>
        </dependency>

1)push方式

  • Flume Agent配置文件:vi flumeStream
a1.sources = s1
a1.channels = c1
a1.sinks = k1

a1.sources.s1.type = netcat
a1.sources.s1.bind = hadoop01
a1.sources.s1.port = 44444
a1.sources.s1.channels = c1

a1.channels.c1.type =memory

a1.sinks.k1.channel = c1

a1.sinks.k1.type = avro
a1.sinks.k1.hostname = hadoop01
a1.sinks.k1.port = 55555

  • 代码
package flume

import org.apache.spark.SparkConf
import org.apache.spark.streaming.flume.{
    
    FlumeUtils}
import org.apache.spark.streaming.{
    
    Seconds, StreamingContext}

object FlumePushDemo extends App {
    
    

    //todo 创建一个StreamingContext对象
    val ssc = new StreamingContext(new SparkConf().setAppName("testflume").setMaster("local[*]"),Seconds(5))
    //todo 创建一个 flumeStream
    val flumeStream = FlumeUtils.createStream(ssc,"hadoop01",55555)
    flumeStream.map(x=>new String(x.event.getBody.array()))
      .flatMap(_.split("\\s+")).map((_,1)).reduceByKey(_+_).print()

    ssc.start()
    ssc.awaitTermination()
}

运行方式

  • 1.将jar包上传到linux
  • 2.启动Spark Streaming作业
spark-submit --class flume.FlumePushDemo spark_day05-1.0-SNAPSHOT.jar
  • 3.启动Flume
flume-ng agent -c /opt/install/flume/conf/ -f /opt/install/flume/conf/job/flumeStream -n a1
  • 4.telnet连接44444端口并发送数据
telnet hadoop01 44444
  • 5.效果如下
    在这里插入图片描述

2)pull方式

  • Flume Agent配置文件:vi streaming_pull_flume.conf
agent.sources = s1
agent.channels = c1
agent.sinks = sk1

#设置Source的内省为netcat,使用的channel为c1
agent.sources.s1.type = netcat
agent.sources.s1.bind = hadoop01
agent.sources.s1.port = 44444
agent.sources.s1.channels = c1


#SparkSink,要求flume lib目录存在spark-streaming-flume-sink_2.11-x.x.x.jar
agent.sinks.sk1.type=org.apache.spark.streaming.flume.sink.SparkSink
agent.sinks.sk1.hostname=hadoop01
agent.sinks.sk1.port=55555
agent.sinks.sk1.channel = c1
#设置channel信息
#内存模式
agent.channels.c1.type = memory
agent.channels.c1.capacity = 1000
  • 代码
package flume

import org.apache.spark.SparkConf
import org.apache.spark.streaming.flume.FlumeUtils
import org.apache.spark.streaming.{
    
    Seconds, StreamingContext}

object FlumePushDemo2 extends App {
    
    

    //todo 创建一个StreamingContext对象
    val ssc = new StreamingContext(new SparkConf().setAppName("testflume").setMaster("local[*]"),Seconds(5))
    //todo 创建一个 flumeStream
    FlumeUtils.createPollingStream(ssc,"hadoop01",55555).map(x=>new String(x.event.getBody.array()))
      .flatMap(_.split("\\s+")).map((_,1)).reduceByKey(_+_).print()

    ssc.start()
    ssc.awaitTermination()
}

运行方式

  • 1.将jar包上传到linux
  • 2.启动Flume
flume-ng agent -c /opt/install/flume/conf/ -f /opt/install/flume/conf/job/streaming_pull_flume.conf -n agent
  • 3.启动Spark Streaming作业
spark-submit --class flume.FlumePushDemo2 spark_day05-1.0-SNAPSHOT.jar
  • 4.telnet连接44444端口并发送数据
telnet hadoop01 44444
  • 5.效果如下
    在这里插入图片描述

5.Spark Streaming整合Kafka

  • 推荐使用:直接方式
  • Kafka依赖
  <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-streaming-kafka-0-10_2.11</artifactId>
            <version>${spark.version}</version>
  </dependency>
  <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
            <version>2.0.0</version>
  </dependency>
        
  • 代码
package cn.kafaka

import org.apache.kafka.clients.consumer.{
    
    ConsumerConfig, ConsumerRecord}
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.{
    
    DStream, InputDStream}
import org.apache.spark.streaming.kafka010.{
    
    ConsumerStrategies, KafkaUtils, LocationStrategies}
import org.apache.spark.streaming.{
    
    Seconds, StreamingContext}


object SparkKafkaDirectDemo extends App {
    
    
  //TODO 创建streamcontext
  val kafkaParams = Map(
    (ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG->"hadoop01:9092"),
    (ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG->"org.apache.kafka.common.serialization.StringDeserializer"),
    (ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG->"org.apache.kafka.common.serialization.StringDeserializer"),
    (ConsumerConfig.GROUP_ID_CONFIG->"kafka_01")
  )
  val ssc = new StreamingContext(new SparkConf().setAppName("testkafka").setMaster("local[*]"),Seconds(5))
  val message: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream(ssc, LocationStrategies
    .PreferConsistent, ConsumerStrategies
    .Subscribe(Set("testPartition"), kafkaParams))
  val value: DStream[(String, Int)] = message.map(x=>x.value()).flatMap(_.split("\\s+")).map((_,1)).reduceByKey(_+_)
  value.print()

  ssc.start()
  ssc.awaitTermination()
}
  • 运行代码
  • linux上生产数据:kafka-console-producer.sh --topic testPartition --broker-list hadoop01:9092
  • 结果如下
    在这里插入图片描述

五.Spark Streaming优化策略

减少批处理时间

  • 数据接收并发度
  • 数据处理并发度
  • 任务启动开销

设置合适的批次间隔
内存调优

  • DStream持久化级别
  • 清除老数据
  • CMS垃圾回收器
  • 其他:使用堆外内存持久化RDD

猜你喜欢

转载自blog.csdn.net/sun_0128/article/details/108094872