Spark Streaming使用Flume作为数据源

官网地址: Flume Integration Guide 

1、Spark Streaming 2.3.1适配 Flume 1.6.0,在Spark Streaming 2.3.0之后对flume的支持已被标记为过时。主要由于flume直接对接Spark Streaming 会造成Spark Streaming压力过大,特别是高峰期的时候(在之间加一层消息队列会好得多)。但由于很多公司可能仍然在用,故简单做一下介绍。

2、有两种方式可以让Spark Streaming集成Flume并从Flume接受数据。以下分别介绍这两种方式。


一、基于Flume的Push模式(Flume-style Push-based Approach)

    这种方式Spark Streaming会建立一个Receiver,这个Receiver起到一个相当于Flume的Avro Agent的作用,Flume可以将数据推送到这个Receiver。以下是详细的配置步骤。

1、一般要求

    在你的集群中选择一台机器,这台机器需要满足一下条件:

          A.当Spark Streaming+Flume的应用程序运行时,有一个Spark的Worker节点运行在这台机器上。

          B.Flume通过配置后可以将数据推送到这台机器的端口上。

2、配置Flume

    通过以下的配置文件可以将数据发送到Avro Sink。

agent.sinks = avroSink
agent.sinks.avroSink.type = avro
agent.sinks.avroSink.channel = memoryChannel
agent.sinks.avroSink.hostname = <所选机器的IP>
agent.sinks.avroSink.port = <所选机器的端口>

3、配置Spark Streaming应用程序

A.添加依赖

    org.apache.spark 

    spark-streaming-flume_2.11
    ${spark.version}

B.在Streaming应用程序的代码中,导入一个FlumeUtils类并创建input DStream。

import org.apache.spark.streaming.flume._

 val flumeStream = FlumeUtils.createStream(streamingContext, [所选机器ip], [所选机器端口])

4、测试

A.直接运行代码

package com.ruozedata.streaming

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

object FlumePushApp {

  def main(args: Array[String]) {

    val Array(hostname, port) = args

    val sparkConf = new SparkConf()
      .setAppName("FlumePushApp")
      .setMaster("local[2]")
    val ssc = new StreamingContext(sparkConf, Seconds(10))

    val lines = FlumeUtils.createStream(ssc,hostname,port.toInt) 
/*由于createStream返回的DStream类型为SparkFlumeEvent,而不是String,故此时split方法无法使用
*为了能够使用split,我们执行了以下的map操作
*/
    lines.map(x => new String(x.event.getBody.array()).trim)
      .flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).print()

    ssc.start()
    ssc.awaitTermination()
  }

}

//以上代码运行会出错,因为args没有传参数。

传参流程:右上角FlumePushApp-->Edit Configurations-->program arguments-->填写满足条件的参数


接着:

Spark Streaming使用Flume作为数据源_第1张图片

B.打成jar包上传运行

先启动spark-submit

./spark-submit --master local[2] \
--class com.ruozedata.streaming.FlumePullApp \
--packages org.apache.spark:spark-streaming-flume_2.11:2.2.0 \      //相当于添加依赖,需要集群能够访问外网
--name FlumePushApp \
/home/hadoop/lib/train-scala-1.0.jar \

localhost 41414

再启动flume

./flume-ng agent \
--name a1 \
--conf $FLUME_HOME/conf \
--conf-file $FLUME_HOME/conf/nc-memory-flume.conf \

-Dflume.root.logger=INFO,console

在另一台客户端执行 telnet localhost 44444,此时在该客户端输入数据,另一客户端会实时打印出处理结果

//mvn clean packages 打包只包含源码不包含依赖包

//以上解决方式需要集群能够访问外网,不能访问外网时可参照以下步骤

a。把不所有需要打包进来的依赖全部添加provided

Spark Streaming使用Flume作为数据源_第2张图片

b。在pom.xml文件中添加plugin


maven-assembly-plugin







jar-with-dependencies



c。编译时使用 mvn assembly:assembly来打包

二、基于自定义sink的pull模式Pull-based Approach using a Custom Sink

1、一般要求

 不同于Flume直接将数据推送到Spark Streaming中,这种方法自定义一个满足以下条件的Flume Sink:

  • Flume将数据推送到sink中,并且数据保持buffered状态
  • Spark Streaming使用一个可靠的Flume接收器(reliable Flume receiver )和转换器(transaction)从sink拉取数据.只要当数据被接收并且被Spark Streaming备份后,转换器才运行成功.
和第一种方式相比,这种方式更可靠,有更好的 容错能力 选择一台运行在一个Flume agent中的普通sink节点的机器.Flume其他的pipeline配置成向该agent发送数据.Spark集群中的机器应该可以访问到选为sink节点的那台机器。

2、添加依赖

groupId = org.apache.spark
 artifactId = spark-streaming-flume-sink_2.11
 version = 2.3.1
groupId = org.scala-lang
 artifactId = scala-library
 version = 2.11.8
groupId = org.apache.commons
 artifactId = commons-lang3
 version = 3.5

3、配置flume

a1.sinks.k1.type = org.apache.spark.streaming.flume.sink.SparkSink
a1.sinks.k1.hostname = localhost

a1.sinks.k1.port = 41414

4、测试

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

object FlumePullApp {

  def main(args: Array[String]) {

    val Array(hostname, port) = args

    val sparkConf = new SparkConf()
      .setAppName("FlumePullApp")
      .setMaster("local[2]")
    val ssc = new StreamingContext(sparkConf, Seconds(10))

    val lines = FlumeUtils.createPollingStream(ssc, hostname,port.toInt)
    lines.map(x => new String(x.event.getBody.array()).trim)
      .flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).print()

    ssc.start()
    ssc.awaitTermination()
  }

}

5、启动

先启动Flume

./flume-ng agent \
--name a1 \
--conf $FLUME_HOME/conf \
--conf-file $FLUME_HOME/conf/nc-memory-flume2.conf \

-Dflume.root.logger=INFO,console

然后再另一客户端启动 telnet localhost 44444

最后启动Spark Streaming

./spark-submit --master local[2] \
--class com.ruozedata.streaming.FlumePullApp \
--packages org.apache.spark:spark-streaming-flume_2.11:2.2.0 \    
--name FlumePushApp \
/home/hadoop/lib/train-scala-1.0.jar \

localhost 41414


此时在telnet端输入数据,立马出处理结果。


第二种方式更好


最后来两个与题目无关的Spark Streaming小例子

1、黑名单过滤

import org.apache.spark.{SparkConf, SparkContext}

import scala.collection.mutable.ListBuffer

/**
  * Created by ruozedata on 2018/4/15.
  */
object FilterApp {

  def main(args: Array[String]) {

    val sparkConf = new SparkConf()
      .setAppName("FilterApp")
      .setMaster("local[2]")
    val sc = new SparkContext(sparkConf)

    // (骚马,true)
    val blackTuple = new ListBuffer[(String,Boolean)]
    blackTuple.append(("sm",true))  //true为了方便join
    val blacksRDD = sc.parallelize(blackTuple)  //将数组转成RDD

    // 准备测试数据: log
    val input = new ListBuffer[(String,String)]
    input.append(("su","20180808,su,M,20"))
    input.append(("kk","20180808,kk,M,20"))
    input.append(("sm","20180808,sm,M,20"))
    val inputRDD = sc.parallelize(input)

    // TODO... 过滤掉黑名单用户
    val joinRDD = inputRDD.leftOuterJoin(blacksRDD) 
    joinRDD.filter(x => {
      x._2._2.getOrElse(false) != true
    }).map(_._2._1).foreach(println)
  //x._2._2为true或none,x._2._1为日志本身
    sc.stop()
  }

}

2、改造例子1,将其使用Spark Streaming来完成

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

object StreamingFilterApp {

  def main(args: Array[String]) {

    val sparkConf = new SparkConf()
      .setAppName("SocketWordCountApp")
      .setMaster("local[2]")
    val ssc = new StreamingContext(sparkConf, Seconds(10))

    val blacks = List("sm","su")
    val blacksRDD = ssc.sparkContext.parallelize(blacks).map(x=>(x,true))

    val lines = ssc.socketTextStream("hadoop000",9997)
    // (su, log of su)
    val clickLogDstream = lines.map(x => (x.split(",")(1), x)).transform(rdd => {
      rdd.leftOuterJoin(blacksRDD)
        .filter(x => {
          // _2 : (string,option)  _2:option
          x._2._2.getOrElse(false) != true
      }).map(_._2._1)
 
//x._2._2为true或none,x._2._1为日志本身
    })

    clickLogDstream.print()

    ssc.start()
    ssc.awaitTermination()
  }

}

你可能感兴趣的:(Flume,hadoop周边生态)