SparkStreaming讲解与使用

一、SparkStreaming概述

Spark内置对象:

sparkconf:

SparkContext的初始化需要一个SparkConf对象,SparkConf包含了Spark集群配置的各种参数。

SparkContext:

**SparkContext为Spark的主要入口点 ,SparkContext用于连接Spark集群、创建RDD、累加器(accumlator)、广播变量(broadcast variables),所以说SparkContext为Spark程序的根本都不为过** 

SqlContext:

SQLContext是SQL进行结构化数据处理的入口,可以通过它进行DataFrame的创建及SQL的执行 

StreamingContext:

创建应用程序主入口,并连上Driver上接收数据9999端口写入源数据 

 

 

数据能够被推到文件系统和数据库以及仪表板。

SparkStreaming会实时接收到数据流,将其拆分成很多批次,再被Spark引擎处理,得到最终的结果(批次)

 

软件之间相互协作:

启动一个软件----启动一个进程------请求类型http/hdfs-------端口(服务进行监听)-----另一个软件进行访问------输入命令通过端口发送--------通过端口进行返回

 

不同计算机之间的相互协作

 

客户端-----目标服务交互-----配置一致

 

Kafka(消息中间件,能够以容错的方式存储流式数据)----数据计算-----能够对实时采集的数据进行计算----历史数据存储-------合并历史数据----延迟性比较低的效果。

 

特点:

1. 易用

2. 容错

3. 易整合

二、词频统计案例

1. spark-submit

root用户

yum install nc

bigdata用户

Nc 监听某个端口有没有数据进来/还可以发送数据

在Linux下安装netcat: root下 yum install nc

在linux下解压压缩包到c://windows/System32

nc -lk 9999

spark-submit --master local[2] \
--class org.apache.spark.examples.streaming.NetworkWordCount \
$SPARK_HOME/lib/spark-examples-1.6.3-hadoop2.6.0.jar hh 9999

 

参数解释类的全路径,jar包的位置 主机 端口号

 

2. spark-shell

spark-shell --master local[2]    //根据CPU的核数分配线程,线程数为2

Intel超线程实际核数4有4个模拟核数
import org.apache.spark.streaming.{Seconds, StreamingContext}
val ssc = new StreamingContext(sc, Se6conds(1))
val lines = ssc.socketTextStream("hh", 8888)
val words = lines.flatMap(_.split(" "))
val wordCounts = words.map(x => (x, 1)).reduceByKey(_ + _)
wordCounts.print()
ssc.start()
ssc.awaitTermination()

三、SparkStreaming运行原理

1. 粗粒度

SparkStreaming接收实时数据流

根据给定的时间间隔进行拆分,变成小批次的处理

底层由Spark引擎计算

处理后的结果也是按批次的

2. 细粒度

在Driver端启动StreamingContext

同时启动Receiver

将接收到的数据拆分暂存在内存中

Receiver会向StreamingContext报告信息

StreamingContext会按预设的周期发布job

SparkContext会将job分发给Executor

四、StreamingContext

可以使用SparkConf获得,也可以使用SparkContext获得

使用Streaming需要定义数据输入的源(DStream)

需要定义计算模式,最终作用在DStream

使用StreamingContext的start方法启动

使用awaitTermination方法将停止方式设置为手动停止或由于某些错误停止

手工停止时可以使用stop方法

五、DStream

代表一个持续化的数据流,代表一系列的RDD,其中的每一个RDD都包含了一个批次的数据。

任何对于DStream的操作都相当于是对于每个RDD做相同的作。

六、Input DStream和Receivers

每一个Input DStream都需要关联一个Receiver

1. Input DStream

代表从源头接收的数据流

2. Receivers

从源头接收数据(除文件系统外),存放在内存中,在处理时使用

七、Transformation和Operations

1. Transformation

和RDD的操作类似,允许对Input DStream的数据进行修改

2. Operations

可以使用Output Operations输出计算结果,除此之外还支持窗口函数等

五、SparkStreaming处理Socket数据

build.sbt中添加依赖:

libraryDependencies += "org.apache.spark" %% "spark-streaming" % "1.6.3" % "provided"

 

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

object NetworkDataStream {
  def main(args: Array[String]): Unit = {
 //初始化配置-如果数据源不是文件类型-local[2](receiver)
val conf=new SparkConf().setMaster("local[*]").setAppName("MyStreaming")
//创建实时处理流对象,添加配置文件,添加时间间隔
val ssc=new StreamingContext(conf,Seconds(3))
//接下来是计算过程,指定数据来源,以及存储的数据类型
val lines:ReceiverInputDStream[String]=ssc.socketTextStream("localhost",9999)
//对数据进行处理
val result:DStream[(String,Int)]=lines.flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_)
//输出处理结果
result.print()
//开启进程
ssc.start()
//出现异常自动停止进程
ssc.awaitTermination()
//手动停止进程
//ssc.stop()


  }
}

运行在window上 需要安装netcat 在dos下启动nc -pl 9999进行监听

在idea中启动程序

注意:需要sbt配置文件将省略压缩包打开

spark-streaming" % "1.6.3"//% "provided"

 

程序运行到虚拟机:


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

object NetWorkData2 {

  def main(args: Array[String]): Unit = {

    val sparkConf=new SparkConf().setAppName("Streaming").setMaster("local[2]")

    val ssc=new StreamingContext(sparkConf,Seconds(3))

    ssc.checkpoint("hdfs://hh:8020/ck-2018-8-19-001")

    val lines:ReceiverInputDStream[String]=ssc.socketTextStream("hh",9988)

    val words:DStream[String]=lines.flatMap(_.split(" "))

    val result:DStream[(String,Int)]=words.map((_,1)).reduceByKey(_+_)

    result.print()

    ssc.start()
    ssc.awaitTermination()
  }

}

 

 

第一步:程序打包

File---project  structure---Artifacts----+----第一项----第二项---moduel----创建的类---OK-----APPLY----OK

 

BUILD----build artifacts-----项目名---build----out中复制压缩包

 

第二步:上传到linux的datas中

 

启动两个客户端

第一个:nc -lk 9988

第二个:spark-submit --class com.qfedu.streaming.NetWorkData2 --master spark://hh:7077 untitled.jar

注意:端口启动过的就不要用了看不出效果:程序执行第二遍的话一定换一个端口,或重启各个服务

八、SparkStreaming处理文件系统数据

Streaming会监控指定的文件夹,当有任何文件被创建时将会进行计算(但不支持子目录)

数据文件必须含有相同的格式。

 

文件必须通过移动或重命名的方式进行创建

 

一旦发生移动,文件不能被改变,新增的数据不会被读取

spark-shell
import org.apache.spark.streaming.{Seconds, StreamingContext}
val ssc = new StreamingContext(sc,Seconds(3))
val lines = ssc.textFileStream("file:///home/hadoop/dataDir")
val result = lines.flatMap(_.split(" ")).map((_,1)).reduceByKey(_ + _)
result.print()
ssc.start()
ssc.awaitTermination()

九、UpdateStateByKey

UpdateStateByKey允许在使用持续更新的数据时保持状态信息,将最新的计算结果与历史结果做合并

 

定义一个状态-可以是任意的数据类型

 

定义一个状态更新的方法-如何使用先前状态和输入流中的新值更新

十、流式数据单词计数案例

import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.{Seconds, StreamingContext}

object StatefulWordCount {
  def main(args: Array[String]): Unit = {
    val sparkConf = new
      SparkConf().setMaster("local[2]").setAppName("Streaming");
  val ssc = new StreamingContext(sparkConf,Seconds(3))
 //将当前的数据进行一个存储,基本上带状态的算子都会指定他
  ssc.checkpoint(".")
  val lines = ssc.socketTextStream("localhost",9999)
  val result: DStream[(String, Int)] = lines.flatMap(_.split(" ").map((_,1)))
  val state = result.updateStateByKey(updateFunction)
  state.print()
  ssc.start()
  ssc.awaitTermination()
}
//seq中传进去的值为(1,1,1,1,1)
def updateFunction(curValues: Seq[Int], preValues:Option[Int]): Option[Int] = {
  val curCount = curValues.sum
  val preCount = preValues.getOrElse(0)
  Some(curCount + preCount)

  }
}

十一、Streaming写入结果至RDBMS

将统计结果写入MySQL,同时尽量保证高效运行

工程构建

libraryDependencies += "mysql" % "mysql-connector-java" % "5.1.46"

create table wordCount(
    word varchar(50) default null,
    counts int(11) default null
)

1. 实现一(错误示范)

连接出现序列化异常(数据库有最大连接数)

import org.apache.spark.SparkConf
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.dstream.DStream

object ForeachRDD {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local[2]").setAppName("Streaming");
    val ssc = new StreamingContext(sparkConf,Seconds(3))
    ssc.checkpoint(".")
    val lines = ssc.socketTextStream("localhost",9999)
    val result: DStream[(String, Int)] = lines.flatMap(_.split(" ").map((_,1))).reduceByKey(_ + _)
    result.foreachRDD(rdd => {
      val connection = createNewConnection()
      rdd.foreach(record => {
        val sql = "insert into wordCount values ('" + record._1 + "'," + record._2 + ")"
        connection.createStatement().execute(sql)
      })
    })
    ssc.start()
    ssc.awaitTermination()
  }

  def createNewConnection() = {
    Class.forName("com.mysql.jdbc.Driver")
    DriverManager.getConnection("jdbc:mysql://localhost:3306/teach01","root","mysql")
  }
}

2. 实现二(正确)

import java.sql.DriverManager
import org.apache.spark.SparkConf
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.dstream.DStream

object ForeachRDD {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local[2]").setAppName("Streaming");
    val ssc = new StreamingContext(sparkConf,Seconds(3))
    val lines = ssc.socketTextStream("localhost",9999)
    val result: DStream[(String, Int)] = lines.flatMap(_.split(" ").map((_,1))).reduceByKey(_ + _)
    result.print()
    result.foreachRDD(rdd => {
      rdd.foreachPartition(partitionOfRecords => {
          val connection = createNewConnection()
          partitionOfRecords.foreach(record => {
            val sql = "insert into wordCount values ('" + record._1 + "'," + record._2 + ")"
            connection.createStatement().execute(sql)
          })
          connection.close()
      })
    })
    ssc.start()
    ssc.awaitTermination()
  }

  def createNewConnection() = {
    Class.forName("com.mysql.jdbc.Driver")
    DriverManager.getConnection("jdbc:mysql://localhost:3306/teach01","root","mysql")
  }
}

十二、黑名单过滤

数据源分为两部分:访问日志(流式数据)和黑名单列表(离线数据),在流式数据中将黑名单中出现的数据进行过滤

数据维度:点击时间,用户名称

数据分隔符:逗号

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

object BlackListFilter {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local[2]").setAppName("Streaming");
    val ssc = new StreamingContext(sparkConf,Seconds(3))
//zhangsan true lisi true

    val blackListRDD =

ssc.sparkContext.parallelize(List("ZhangSan","LiSi")).map((_,true))
    val lines = ssc.socketTextStream("localhost",9999)
    val result = lines.map(log => (log.split(",")(1),log)).transform(rdd => {
      rdd.leftOuterJoin(blackListRDD).filter(_._2._2.getOrElse(false) != true).map(_._1)
    })
    result.print()
    ssc.start()
    ssc.awaitTermination()
  }
}

 

十三.SparkStreaming整合FlumeSparkStreaming集群运行

Push方式实现Pull方式实现

(1)push -> 推送 -> Streaming(DataServer):Streaming先启动

接收数据时填写虚拟网卡相关ip地址,不要填写localhost

(2)pull -> 拉取 -> Flume先启动

一、Push-based(先启动程序端再启动flume端)

1. Flume配置

# Name the components on this agent
a1.sources = r1
a1.sinks = k1
a1.channels = c1
# Describe/configure the source
a1.sources.r1.type = exec

#监控一个文件
a1.sources.r1.command = tail -f /home/bigdata/webapp.log
# Describe the sink
a1.sinks.k1.type = avro //Avro是一个数据序列化系统,设计用于支持大批量数据交换的应用。
a1.sinks.k1.hostname = hh  //指定需要将数据传送给那台机器,那台机器是Steaming中所在的服务器
a1.sinks.k1.port = 8888
# Use a channel which buffers events in memory
a1.channels.c1.type = memory
# Bind the source and sink to the channel
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1

 

flume-ng agent --name a1 --conf $FLUME_HOME/conf --conf-file $FLUME_HOME/conf/file-memory-push.conf -Dflume.root.logger=INFO.console

 

***********在启动flume测试的时候,如果没有程序对avro指定的主机端口进行监听flume会主线异常,所以先启动程序再启动flume

***************************

2. SparkStreaming开发(监控一个文件,并实现实时的单词计数,并且与历史计数结果进行组合得到最终结果)

*******环境构建:

libraryDependencies += "org.apache.spark" %% "spark-streaming-flume" % "1.6.3"

去除依赖(去除两条依赖的重复的包):

libraryDependencies += "org.apache.spark" %% "spark-streaming" % "1.6.3" exclude("javax.servlet", "*")

 

如果不去除依赖包的话程序将会报错

java.lang.SecurityException: class "javax.servlet.FilterRegistration"'s signer information does not match signer information of other classes in the same package

at java.lang.ClassLoader.checkCerts(ClassLoader.java:898)

at java.lang.ClassLoader.preDefineClass(ClassLoader.java:668)

 

 

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

object PushStreaming {

  def main(args: Array[String]): Unit = {
    if(args.length<2){
      println("need two params :<hostname>,<port>")
      System.exit(-1)
    }
    val Array(hostname,port) = args
    val conf=new SparkConf().setMaster("local[2]").setAppName("MyStreamingFlume")
    val ssc=new StreamingContext(conf,Seconds(3))
    ssc.checkpoint(".")
    val flumeStream= FlumeUtils.createStream(ssc,hostname,port.toInt)
    //获取flume数据,flume中的数据有head和body要取出body中的数据
    val result= flumeStream.map(data => new String(data.event.getBody.array())).flatMap(_.split(" ")).map((_,1))
    val state=result.updateStateByKey(updateFunction)
    state.print()
    ssc.start()
    ssc.awaitTermination()
  }
  def updateFunction(curValues: Seq[Int], preValues:Option[Int]): Option[Int] = {
    val curCount = curValues.sum
    val preCount = preValues.getOrElse(0)
    Some(curCount + preCount)
  }

}

 

 

打包运行

% "provided"  打包之前加上这个

 

libraryDependencies += "org.apache.kafka" % "kafka-clients" % "0.10.2.2"% "provided"
libraryDependencies += "org.apache.spark" %% "spark-streaming-flume" % "1.6.3"
libraryDependencies += "org.apache.spark" %% "spark-streaming" % "1.6.3" % "provided"exclude("javax.servlet", "*")

 

 

两种方式:

******先启动程序再启动flume**********

推荐:

spark-submit --master local[2] --class com.qfedu.streaming.FlumePush /home/bigdata/streaming.jar bigdata 8888

 

spark-submit --class com.qfedu.streaming.FlumePush --master spark://hh:7077 untitled.jar hh 8888

 

shutdown()

将线程池状态置为SHUTDOWN,并不会立即停止:

停止接收外部submit的任务

内部正在跑的任务和队列里等待的任务,会执行完

等到第二步完成后,才真正停止

shutdownNow()

将线程池状态置为STOP。企图立即停止,事实上不一定:

跟shutdown()一样,先停止接收外部提交的任务

忽略队列里等待的任务

尝试将正在跑的任务interrupt中断

返回未执行的任务列表

 

awaitTermination(long timeOut, TimeUnit unit)

当前线程阻塞,直到

等所有已提交的任务(包括正在跑的和队列中等待的)执行完

或者等超时时间到

或者线程被中断,抛出InterruptedException

然后返回true(shutdown请求后所有任务执行完毕)或false(已超时)

 

实验发现,shuntdown()和awaitTermination()效果差不多,方法执行之后,都要等到提交的任务全部执行完才停。

二、Pull-based(用的比较多的,消费完成再拉取)

1. Flume配置

# Name the components on this agent
a1.sources = r1
a1.sinks = k1
a1.channels = c1
# Describe/configure the source
a1.sources.r1.type = exec
a1.sources.r1.command = tail -f /home/bigdata/webapp.log
# Describe the sink
a1.sinks.k1.type = org.apache.spark.streaming.flume.sink.SparkSink
a1.sinks.k1.hostname = hh
a1.sinks.k1.port = 8888
# Use a channel which buffers events in memory
a1.channels.c1.type = memory
# Bind the source and sink to the channel
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1

 

复制jar包spark-streaming-flume-sink_2.10-1.6.3.jar至flume的lib目录下

运行flume

flume-ng agent --name a1 --conf $FLUME_HOME/conf --conf-file $FLUME_HOME/conf/example-pull.conf -Dflume.root.logger=INFO.console

2. SparkStreaming开发

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

object FlumePull {
  def main(args: Array[String]): Unit = {
    if (args.length < 2){
      println("needs two params:<hostname> <port>")
      System.exit(-1)
    }
    val Array(hostname, port) = args
    val sparkConf = new SparkConf().setMas

ter("local[2]").setAppName("FlumeStreaming")
    val ssc = new StreamingContext(sparkConf, Seconds(3))
    val flumeStream = FlumeUtils.createPollingStream(ssc,hostname,port.toInt)
 val products = flumeStream.map(data => new

String(data.event.getBody.array())).flatMap(_.split("-")(1))
    val result = products.map((_,1)).reduceByKey(_ + _)
//print方法必须需要有,标记着计算流程的结束,不标记的话会报错  

 result.print()
    ssc.start()
    ssc.awaitTermination()
  }
}

打包运行

spark-submit --class com.qfedu.streaming.FlumePull --master spark://hh:7077 untitled.jar hh 8888

十四.SparkStreaming整合KafkaSparkStreaming集群运行

问题产生:当SparkStream在flume中拉取数据的时候,当flume每次启动都会读取之前的数据,此时要引进kafka的技术,kafka使用偏移量 记录数据是否被消费。

 

kafka技术的优点:

(1) 解耦

 

在项目启动之初来预测将来项目会碰到什么需求,是极其困难的。消息系统在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口。这允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。

 

(2) 冗余

 

有些情况下,处理数据的过程会失败。除非数据被持久化,否则将造成丢失。消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的"插入-获取-删除"范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。

 

(3) 扩展性

 

因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。不需要改变代码、不需要调节参数。扩展就像调大电力按钮一样简单。

 

(4) 灵活性 & 峰值处理能力

 

在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见;如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。

 

(5) 顺序保证

 

在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。Kafka保证一个Partition内的消息的有序性。

 

(6) 缓冲

 

在任何重要的系统中,都会有需要不同的处理时间的元素。例如,加载一张图片比应用过滤器花费更少的时间。消息队列通过一个缓冲层来帮助任务最高效率的执行———写入队列的处理会尽可能的快速。该缓冲有助于控制和优化数据流经过系统的速度。

 

 

Receiver是使用Kafka的高层次Consumer API来实现的。receiver从Kafka中获取的数据都是存储在Spark Executor的内存中的,然后Spark Streaming启动的job会去处理那些数据。然而,在默认的配置下,这种方式可能会因为底层的失败而丢失数据。如果要启用高可靠机制,让数据零丢失,就必须启用Spark Streaming的预写日志机制(Write Ahead Log,WAL)。该机制会同步地将接收到的Kafka数据写入分布式文件系统(比如HDFS)上的预写日志中。所以,即使底层节点出现了失败,也可以使用预写日志中的数据进行恢复,但是效率会下降。

 

direct 这种方式会周期性地查询Kafka,来获得每个topic+partition的最新的offset,从而定义每个batch的offset的范围。当处理数据的job启动时,就会使用Kafka的简单consumer api来获取Kafka指定offset范围的数据。

这种方式有如下优点:
1、简化并行读取:如果要读取多个partition,不需要创建多个输入DStream然后对它们进行union操作。Spark会创建跟Kafka partition一样多的RDD partition,并且会并行从Kafka中读取数据。所以在Kafka partition和RDD partition之间,有一个一对一的映射关系。

2、高性能:如果要保证零数据丢失,在基于receiver的方式中,需要开启WAL机制。这种方式其实效率低下,因为数据实际上被复制了两份,Kafka自己本身就有高可靠的机制,会对数据复制一份,而这里又会复制一份到WAL中。而基于direct的方式,不依赖Receiver,不需要开启WAL机制,只要Kafka中作了数据的复制,那么就可以通过Kafka的副本进行恢复。

 

3、一次且仅一次的事务机制:
    基于receiver的方式,是使用Kafka的高阶API来在ZooKeeper中保存消费过的offset的。这是消费Kafka数据的传统方式。这种方式配合着WAL机制可以保证数据零丢失的高可靠性,但是却无法保证数据被处理一次且仅一次,可能会处理两次。因为Spark和ZooKeeper之间可能是不同步的。
    基于direct的方式,使用kafka的简单api,Spark Streaming自己就负责追踪消费的offset,并保存在checkpoint中。Spark自己一定是同步的,因此可以保证数据是消费一次且仅消费一次。

 

两者进行比较应用场景:

(1)Receiver:日志信息(基本数据类型+分隔符)

(2)Direct:复杂的实体类

一、Kafka配置

在这之前开启tomcat的web程序

./srart-all.sh

 

启动Kafka

一.kafka-server-start.sh -daemon $KAFKA_HOME/config/server-0.properties

创建Topic

kafka-topics.sh --create --zookeeper hh:2181 --replication-factor 1 --partitions 1 --topic product_topic

使用控制台生产脚本

kafka-console-producer.sh --broker-list hh:9092 --topic product_topic

flume的配置

 

# Name the components on this agent

a1.sources = r1

a1.sinks = k1

a1.channels = c1

 

# Describe/configure the source

a1.sources.r1.type = exec

a1.sources.r1.command = tail -f /home/hadoop/data.log

 

# Describe the sink

a1.sinks.k1.type = org.apache.flume.sink.kafka.KafkaSink

a1.sinks.k1.kafka.bootstrap.servers= hh:9092

a1.sinks.k1.kafka.topic = product_topic

 

# Use a channel which buffers events in memory

a1.channels.c1.type = memory

 

# Bind the source and sink to the channel

a1.sources.r1.channels = c1

a1.sinks.k1.channel = c1

 

二.启动flume:

flume-ng agent --name a1 --conf $FLUME_HOME/conf --conf-file $FLUME_HOME/conf/example-Streaming.conf -Dflume.root.logger=INFO,console

 

 

启动一个消费者测试:

kafka-console-consumer.sh --zookeeper hh:2181 --topic product_topic

 

到此为止实现了,flum监控web的日志文件,kafka在flume中拉取数据

 

二、Receiver-based拉取进行计算

环境构建:libraryDependencies += "org.apache.spark" %% "spark-streaming-kafka" % "1.6.3"

import org.apache.spark.SparkConf
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}
object KafkaReceiver {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local[2]").setAppName("Kafka")
    val ssc = new StreamingContext(sparkConf,Seconds(3))
    val Array(zookeeper,groupId,topicNames,topicPartition) = args

    val topicMap = topicNames.split(",").map((_,topicPartition.toInt)).toMap

    val kafkaStream = KafkaUtils.createStream(ssc,zookeeper,groupId,topicMap)
    val products = kafkaStream.filter(data => "click".equals(data._2.split("-")(0))).flatMap(_._2.split("-")(1))
    val result = products.map((_,1)).reduceByKey(_ + _)
    result.print()
    ssc.start()
    ssc.awaitTermination()
  }
}

 

 

 

三、启动程序此时完成了streaming的实时计算:

 

求wordcount的案例

package com.china.streaming

import com.china.streaming.PushStreaming.updateFunction
import org.apache.spark.SparkConf
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.flume.FlumeUtils
import org.apache.spark.streaming.kafka.KafkaUtils

object streaming_kafka1 {
  def main(args: Array[String]): Unit = {

    if(args.length<4){
      println("need four params :<hostname>,<port>")
      System.exit(-1)
    }
    val Array(zookeeper,groupId,topicts,partitions) = args

    val topicmap=topicts.split(",").map((_,partitions.toInt)).toMap
    val conf=new SparkConf().setMaster("local[2]").setAppName("MyStreamingkafka")
    val ssc=new StreamingContext(conf,Seconds(3))
    ssc.checkpoint(".")
    val kafkaStream= KafkaUtils.createStream(ssc,zookeeper,groupId,topicmap)
    //获取flume数据,flume中的数据有head和body要取出body中的数据
    val result= kafkaStream.flatMap(_._2.split(" ")).map((_,1))
    val state=result.updateStateByKey(updateFunction)
    //print方法必须写,用来开启计算
    state.print()
    ssc.start()
    ssc.awaitTermination()
  }
  def updateFunction(curValues: Seq[Int], preValues:Option[Int]): Option[Int] = {
    val curCount = curValues.sum
    val preCount = preValues.getOrElse(0)
    Some(curCount + preCount)
  }

}

 

 

edit configrations:    had01:2181 click streaming1,streaming2 1

三、Direct方式拉取数据进行计算


import kafka.serializer.StringDecoder
import org.apache.spark.SparkConf
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}

object KafkaDirect {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local[2]").setAppName("Kafka")
    val ssc = new StreamingContext(sparkConf,Seconds(3))
    val kafkaMap: Map[String,String] = Map(("bootstrap.servers","hh:9092"))
    val topicSet: Set[String] = Set("first_topic","product_topic")
    // 统一编码为UTF-8
    val directKafkaStream = KafkaUtils.createDirectStream[String, String,StringDecoder, StringDecoder](ssc, kafkaMap, topicSet)
    val products = directKafkaStream.filter(data => "click".equals(data._2.split("-")(0))).flatMap(_._2.split("-")(1))
    val result = products.map((_,1)).reduceByKey(_ + _)
    result.print()
    ssc.start()
    ssc.awaitTermination()
  }
}

 

 

开启生产者:

kafka-console-producer.sh --broker-list hh:9092 --topic first_topic

 

启动idea程序:

 

 

生产者生产:

click-1

click-1

click-2

click-2

click-1

click-1

click-2

click-2

 

在web操作,点击按钮测试

十五.SparkStreaming整合Hbase

SparkStreaming整合Hbase本节任务教学目标教学内容一、Hbase安装和启动二、SparkStreaming程序

SparkStreaming整合HbaseSparkStreaming保存计算结果

一、Hbase安装和启动

解压缩

tar -zvxf tar -zvxf hbase-1.2.8-bin.tar.gz

环境变量

vi ~/.bash_profile
export HBASE_HOME=/home/bigdata/hbase-1.2.8
PATH=$PATH:$HBASE_HOME/bin

source ~/.bash_profile

hbase-env.sh

export JAVA_HOME=/home/bigdata/jdk1.8.0_171
export HBASE_MANAGES_ZK=false

hbase-site.xml

<configuration>
        <property>
                <name>hbase.rootdir</name>
                <value>hdfs://bigdata:9000/hbase</value>
        </property>
        <property>
                <name>hbase.cluster.distributed</name>
                <value>true</value>
        </property>
        <property>
                <name>hbase.zookeeper.quorum</name>
                <value>bigdata:2181</value>
        </property>
        <property>
            <name>hbase.master.info.port</name>
            <value>60010</value>
        </property>
</configuration>

regionservers

bigdata

启动Hbase

start-hbase.sh

WEBUI

http://bigdata:60010/

创建表(hbase shell)

create 'product','info'

hbase可以对某个rowkey做增量操作

需求

场景:电商平台产生的商品被浏览数据

数据维度:商品Id,点击次数

分析过程:统计每种商品被点击的次数

结果保存:HBase

 

二、SparkStreaming程序

环境构建

 

 

 

libraryDependencies += "org.apache.spark" %% "spark-streaming-kafka" % "1.6.3" exclude("javax.servlet", "*")

libraryDependencies += "org.apache.hbase" % "hbase-common" % "1.2.8"

libraryDependencies += "org.apache.hbase" % "hbase-client" % "1.2.8"

libraryDependencies += "org.apache.hbase" % "hbase-server" % "1.2.8"

添加连接hbase的配置文件

在scala下建立config文件夹,在文件夹下添加hbase-site.xml文件,由于文件中各个属性都是以键值对的形式存在的,可以直接读取该文件获得配置信息。

在scala下添加properties配置文件

kafkaConsumer.properties

kafkaProducer.properties

log4j.properties

HBase工具类(新创建hbase的package)

连接测试:

import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.hbase.{HBaseConfiguration, TableName}
import org.apache.hadoop.hbase.client.{Connection, ConnectionFactory, Table}
import org.apache.hadoop.hbase.util.Bytes

class HBaseUtil {
  private val conf: Configuration = HBaseConfiguration.create
  conf.addResource("hbase-site.xml")
  private val connection: Connection = ConnectionFactory.createConnection(conf)

  def getTable(tableName: String): Table ={
    val name: TableName = TableName.valueOf(Bytes.toBytes(tableName))
    connection.getTable(name)
  }
}

object HBaseUtil{
  def main(args: Array[String]): Unit = {
    val hBaseUtil = new HBaseUtil
    println(hBaseUtil.getTable("product1").getName)
  }
}

 

 

 

在scala文件夹下创建conf文件夹

 

file---project structure----Modules----找到conf文件夹----resources---OK

实体类

/**
  * 商品点击量
  * @param productId 商品唯一标识
  * @param count 商品被点击次数
  */
case class ProductClickCount(productId: String, count: Long)

HBase数据交互类

 

dao层

 

package com.qfedu.dao

import com.qfedu.hbase.HBaseUtil
import org.apache.hadoop.hbase.client.Table
import org.apache.hadoop.hbase.util.Bytes
import com.qfedu.bean.ProductCount

import scala.collection.mutable.ListBuffer

class ProductDao {

  val tableName = "product"
  val family = "info"
  val qualifier = "count"
  val hbaseUtil = new HBaseUtil

  /**
    * 向HBase中以增长的方式更新数据
    * @param list 当前批次的计算结果
    */
  def saveData(list: ListBuffer[ProductCount]): Unit ={
    val table: Table = hbaseUtil.getTable(tableName)
    // rowKey -> 商品id
    // columnFamily -> 列族名称
    // qualifier -> 列名
    // data -> 更新时传入的数据
    for(data <- list){
      // data.productId -> 商品ID
      // data.count -> 当前批次中该商品被点击的次数
      table.incrementColumnValue(Bytes.toBytes(data.productId),Bytes.toBytes(family),Bytes.toBytes(qualifier),data.count)
    }
  }

}

 

 

 

实现类:

package com.qfedu.streaming

import com.qfedu.bean.ProductCount
import com.qfedu.dao.ProductDao
import kafka.serializer.StringDecoder
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}

import scala.collection.mutable.ListBuffer

object KafkaDirect {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local[3]").setAppName("Kafka")
    val ssc = new StreamingContext(sparkConf,Seconds(10))
    val kafkaMap: Map[String,String] = Map(("bootstrap.servers","hh:9092"))
    val topicSet: Set[String] = Set("first_topic","product_topic")
    // 统一编码为UTF-8
    val directKafkaStream = KafkaUtils.createDirectStream[String, String,StringDecoder, StringDecoder](ssc, kafkaMap, topicSet)
    val products = directKafkaStream.filter(data => "click".equals(data._2.split("-")(0))).flatMap(_._2.split("-")(1))
    val result: DStream[(Char, Int)] = products.map((_, 1)).reduceByKey(_ + _)
    // result:当前批次的计算结果
    result.foreachRDD(rdd => {
      rdd.foreachPartition(records => {
        val productDao = new ProductDao
        val list = new ListBuffer[ProductCount]
        records.foreach(record => {
          val productId = record._1
          val count = record._2
          val product = new ProductCount(productId.toString,count)
          list.append(product)
        })
        productDao.saveData(list)
      })
    })
    result.print()
    ssc.start()
    ssc.awaitTermination()
  }
}

 

 

工具类

import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.hbase.{HBaseConfiguration, TableName}
import org.apache.hadoop.hbase.client._
import org.apache.hadoop.hbase.util.Bytes


class HBaseUtil {
  private val conf: Configuration = HBaseConfiguration.create
  conf.addResource("hbase-site.xml")
  private val connection: Connection = ConnectionFactory.createConnection(conf)

  def getTable(tableName: String): Table ={
    val name: TableName = TableName.valueOf(Bytes.toBytes(tableName))
    connection.getTable(name)
  }

  /**
    * 查询指定rowKey的count
    * @param tableName
    * @return
    */
  def getCount(tableName: String): Unit ={
    val table :Table = getTable(tableName)
    // 通过给定的rowKey查询对应的数据
    val get1 = new Get(Bytes.toBytes("1"))
    val value1: Array[Byte] = table.get(get1).getValue(Bytes.toBytes("info"), Bytes.toBytes("count"))
    println(s"产品1的点击次数:${Bytes.toLong(value1)}")
    val get2 = new Get(Bytes.toBytes("2"))
    val value2 = table.get(get2).getValue(Bytes.toBytes("info"),Bytes.toBytes("count"))
    println(s"产品2的点击次数:${Bytes.toLong(value2)}")
    // 在数据流写入数据之前可能会出现rowKey无对应数据的情况,需要加判空处理
  }

  def putData(tableName: String): Unit = {
    val table :Table = getTable(tableName)
    val put = new Put(Bytes.toBytes("1"))
    // 数据类型必须对应 Long
    put.addColumn(Bytes.toBytes("info"),Bytes.toBytes("count"),Bytes.toBytes(10L))
    table.put(put)
  }
}

object HBaseUtil{
  def main(args: Array[String]): Unit = {
    val hBaseUtil = new HBaseUtil
    hBaseUtil.getCount("product")
    //hBaseUtil.putData("product")
  }
}

 

 

 

启动主类和工具类即可完成实验

 

衍生程序单词计数:

文件中的单词------flume-----kafka------SparkStreaming---------Hbase-----读取控制台

flume的编写

#every name

a1.sources = r1

a1.sinks = k1

a1.channels = c1

#the source

a1.sources.r1.type = exec

a1.sources.r1.command = tail -f /opt/mydata/ccc.txt

#the sink

a1.sinks.k1.type = org.apache.flume.sink.kafka.KafkaSink

a1.sinks.k1.kafka.bootstrap.servers= had01:9092

a1.sinks.k1.kafka.topic = streaming1

#the channer

a1.channels.c1.type = memory

#bind the source and sink to the channel

a1.sources.r1.channels = c1

a1.sinks.k1.channel = c1

 

执行语句:

bin/flume-ng agent --conf ./conf --name a1 --conf-file conf/kafka_streaming.conf -Dflume.root.logger=INFO,console

 

kafka创建两个browke

kafka-server-start.sh -daemon $KAFKA_HOME/config/server-0.properties

 

./kafka-topics.sh --create --zookeeper hh:2181 --replication-factor 1 --partitions 3 --topic first_topic

kafka-topics.sh --create --zookeeper hh:2181 --replication-factor 1 --partitions 1 --topic first_topic

 

kafka-topics.sh --list --zookeeper hh:2181

 

程序

name := "MySparkStremaing"

version := "0.1"

scalaVersion := "2.11.8"

//resolvers += "cloudera" at "https://repository.cloudera.com/artifactory/cloudera-repos/"

libraryDependencies += "org.apache.hbase" % "hbase-common" % "1.2.8"

libraryDependencies += "org.apache.hbase" % "hbase-client" % "1.2.8"

libraryDependencies += "org.apache.hbase" % "hbase-server" % "1.2.8"

libraryDependencies += "org.apache.spark" %% "spark-streaming-kafka" % "1.6.3" exclude ("javax.servlet","*")

libraryDependencies += "org.apache.kafka" % "kafka-clients" % "0.10.2.2"//% "provided"

libraryDependencies += "org.apache.spark" %% "spark-streaming" % "1.6.3" exclude ("javax.servlet","*")//% "provided"

libraryDependencies += "org.apache.spark" %% "spark-streaming-flume" % "1.6.3"

 

 

在scala下建立config文件夹,在文件夹下添加hbase-site.xml文件,由于文件中各个属性都是以键值对的形式存在的,可以直接读取该文件获得配置信息。

在scala下添加properties配置文件

kafkaConsumer.properties

kafkaProducer.properties

log4j.properties

创建bean类

package com.china.hbase.bean

case class MyWord(WordId:String,count:Long) {}

 

 

创建向hbase表添加数据的类

mport scala.collection.mutable.ListBuffer

class wordDao {
  val tableName = "wordCount"
  val family = "info"
  val qualifier = "count"
  val hbaseUtil = new HbaseUtil
  /**
    * 向HBase中以增长的方式更新数据
    * @param list 当前批次的计算结果
    */
  def saveData(list: ListBuffer[MyWord]): Unit ={
    val table: Table = hbaseUtil.getTable(tableName)
    // rowKey -> 商品id
    // columnFamily -> 列族名称
    // qualifier -> 列名
    // data -> 更新时传入的数据
    for(data <- list){
      // data.productId -> 商品ID
      // data.count -> 当前批次中该商品被点击的次数
      table.incrementColumnValue(Bytes.toBytes(data.WordId),Bytes.toBytes(family),Bytes.toBytes(qualifier),data.count)
    }
  }

}

 

 

添加kafka-streaming类

package com.china.hbase.oprate

import com.china.hbase.bean.MyWord
import com.china.hbase.dao.wordDao
import kafka.serializer.StringDecoder
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}

import scala.collection.mutable.ListBuffer
object fromKafka {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local[3]").setAppName("Kafka")
    val ssc = new StreamingContext(sparkConf,Seconds(10))
    val kafkaMap: Map[String,String] = Map(("bootstrap.servers","had01:9092"))
    val topicSet: Set[String] = Set("streaming1","streaming2")
    // 统一编码为UTF-8
    val directKafkaStream = KafkaUtils.createDirectStream[String, String,StringDecoder, StringDecoder](ssc, kafkaMap, topicSet)
    val products = directKafkaStream.flatMap(_._2.split(" ")).map((_,1))
    val result: DStream[(String, Int)] = products.reduceByKey(_ + _)
    // result:当前批次的计算结果
    result.foreachRDD(rdd => {
      rdd.foreachPartition(records => {
        val wordDao = new wordDao
        val list = new ListBuffer[MyWord]
        records.foreach(record => {
          val productId = record._1
          val count = record._2
          val product = new MyWord(productId.toString,count)
          list.append(product)
        })
        wordDao.saveData(list)
      })
    })
    result.print()
    ssc.start()
    ssc.awaitTermination()
  }


}

 

 

添加hbaseutil类

import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.hbase.{Cell, CellUtil, HBaseConfiguration, TableName}
import org.apache.hadoop.hbase.client._
import org.apache.hadoop.hbase.util.Bytes

import scala.collection.mutable.ArrayBuffer

class HbaseUtil{
  //创建配置文件对象
  private val conf: Configuration = HBaseConfiguration.create
  //向conf中加载配置文件
  conf.addResource("hbase-site.xml")
 //创建链接
  private val connection: Connection = ConnectionFactory.createConnection(conf)
//获得表
  def getTable(tableName: String): Table ={
    val name: TableName = TableName.valueOf(Bytes.toBytes(tableName))
    connection.getTable(name)
  }

  /**
    * 查询指定rowKey的count
    * @param tableName
    * @return
    */
  def getCount(tableName: String): Unit ={


    val columnFamily="info"
    val column="count"
    val table :Table = getTable(tableName)
    // 通过给定的rowKey查询对应的数据
    val  scan=new Scan()
    val scanner=table.getScanner(scan)
    var result=scanner.next()
    while (result!=null){
      val get2 = new Get(result.getRow)
      val value2 = table.get(get2).getValue(Bytes.toBytes("info"),Bytes.toBytes("count"))
      println(s"单词:${Bytes.toString(result.getRow)}的出现次数:${Bytes.toLong(value2)}")
      result=scanner.next()
    }
  }

  def putData(tableName: String): Unit = {
    val table :Table = getTable(tableName)
    val put = new Put(Bytes.toBytes("1"))
    // 数据类型必须对应 Long
    put.addColumn(Bytes.toBytes("info"),Bytes.toBytes("count"),Bytes.toBytes(10L))
    table.put(put)
  }
}

object HbaseUtil {
  def main(args: Array[String]): Unit = {
    val hBaseUtil = new HbaseUtil
    hBaseUtil.getCount("wordCount")


  }


}

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值