官网地址: 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.添加依赖
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}
//以上代码运行会出错,因为args没有传参数。
传参流程:右上角FlumePushApp-->Edit Configurations-->program arguments-->填写满足条件的参数
接着:
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。把不所有需要打包进来的依赖全部添加
b。在pom.xml文件中添加plugin
c。编译时使用 mvn assembly:assembly来打包
二、基于自定义sink的pull模式(Pull-based Approach using a Custom Sink)
1、一般要求
不同于Flume直接将数据推送到Spark Streaming中,这种方法自定义一个满足以下条件的Flume 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}
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()
}
}