Apache Flume是一种分布式,可靠且可用的服务,用于高效地收集,汇总和移动大量日志数据。 这里我们学习如何配置Flume和Spark Streaming来接收来自Flume的数据。 提供两种方法来解决这问题。
注意:从Spark 2.3.0开始,不推荐使用Flume支持。个人也不推荐这种架构,数据量小的情况下可能没什么问题,但是再数据量过大的情况下Streaming流式处理是处理不过来的,必定会造成消息堵塞,所以建议加一个消息中间件例如Kafka。
方法1:流水线式推送方式
Flume接收的数据直接交给Spark Streaming处理。 所以这种方法中,Spark Streaming需要一个接收器receiver,作为Flume的Avro代理,Flume可以将数据推送到该接收器。 所以这里要注意local的使用。
启动要求:先启动Spark Streaming进行接收数据再启动Flume.
选型结构:netcat => memory ==> avro sink ==> streaming
import org.apache.spark.SparkConf
import org.apache.spark.streaming.{Seconds, StreamingContext}
object FlumePush {
def main(args: Array[String]): Unit = {
//传入的参数hostname,port
val Array(hostname,port)=args
val sparkConf=new SparkConf().setAppName("FlumePush").setMaster("local[2]")
val ssc=new StreamingContext(sparkConf,Seconds(10))
// import FlumeUtils
import org.apache.spark.streaming.flume._
//create input DStream as follows.
val flumeStream=FlumeUtils.createStream(ssc,hostname,port.toInt)
//词频统计
flumeStream.map(x=>new String(x.event.getBody.array()).trim)
.flatMap(x=>x.split(",")).map((_,1)).reduceByKey(_+_).print()
ssc.start()
ssc.awaitTermination()
}
}
上面我们是通过外面进行传递hostname和port,所以要进行下面的配置
在Programe arguments传递参数。作者使用的时windows本地运行出现各种错误,选择打包上传就不需要在本地配置参数的,直接在spark-submit的时候传递参数。
a1.sources = r1
a1.sinks = k1
a1.channels = c1
# Describe/configure the source
a1.sources.r1.type = netcat
a1.sources.r1.bind = 0.0.0.0
a1.sources.r1.port = 44444
# Describe the sink
a1.sinks.k1.type = avro
a1.sinks.k1.hostname = localhost
a1.sinks.k1.port = 41414
# Use a channel which buffers events in memory
a1.channels.c1.type = memory
a1.channels.c1.capacity=10000
a1.channels.c1.transactionCapacity=1000
# Bind the source and sink to the channel
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
启动命令
./spark-submit --master local[2] \
--class cn.zhangyu.FlumePush \
--name FlumePush \
/home/hadoop/lib/spark_streaming-1.0-SNAPSHOT.jar \
localhost 41414
这时候提交应用程序的时候肯定会报错,因为我们本地并没有FlumeUtils这个jar包,所以要指定--packages g:a:v
参数(有网络才可以用,没有网络可以实现下载好通过–jars参数指定)
./spark-submit --master local[2] \
--class cn.zhangyu.FlumePush \
--packages org.apache.spark:spark-streaming-flume_2.11:2.2.0 \
--name FlumePush \
/home/hadoop/lib/spark_streaming-1.0-SNAPSHOT.jar \
localhost 41414
./flume-ng agent \
--name a1 \
--conf $FLUME_HOME/conf \
--conf-file $FLUME_HOME/conf/flume-sparkStreaming-push \
-Dflume.root.logger=INFO,console
输入数据:
[hadoop@hadoop conf]$ telnet localhost 44444
Trying ::1...
Connected to localhost.
Escape character is '^]'.
a,a,a,b,b,b,c,c,c,d,d,s
OK
a,a,a,b,b,b,c,c,c,d,d,s
OK
结果:
(d,2)
(b,3)
(s,1)
(a,3)
(c,3)
(d,2)
(b,3)
(s,1)
(a,3)
(c,3)
使用该种方式Flume不是直接将数据推送到Spark Streaming,而是运行一个自定义的Flume接收器,它允许执行以下操作。
注意:
1.这时候并不需要Spark Streaming端的receiver了。
2.启动顺序:先启动flume在启动spark streaming。
这确保了比以前的方法更强大的可靠性和容错保证。 但是,这需要配置Flume运行自定义接收器。 以下是配置步骤。
groupId = org.apache.spark
artifactId = spark-streaming-flume-sink_2.11
version = 2.3.0
groupId = org.scala-lang
artifactId = scala-library
version = 2.11.8
groupId = org.apache.commons
artifactId = commons-lang3
version = 3.5
2.配置文件
a1.sources = r1
a1.sinks = k1
a1.channels = c1
# Describe/configure the source
a1.sources.r1.type = netcat
a1.sources.r1.bind = 0.0.0.0
a1.sources.r1.port = 44444
# Describe the sink
a1.sinks.k1.type = org.apache.spark.streaming.flume.sink.SparkSink
a1.sinks.k1.hostname = localhost
a1.sinks.k1.port = 41414
# Use a channel which buffers events in memory
a1.channels.c1.type = memory
a1.channels.c1.capacity=10000
a1.channels.c1.transactionCapacity=1000
# 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/flume-sparkStreaming-poll \
-Dflume.root.logger=INFO,console
./spark-submit --master local[2] \
--class cn.zhangyu.FlumePull \
--packages org.apache.spark:spark-streaming-flume_2.11:2.2.0 \
--name FlumePull \
/home/hadoop/lib/spark_streaming-1.0-SNAPSHOT.jar \
localhost 41414
telnet localhost 44444
a,a,a,b,b,b,c,c,c,d,d,s
(d,2)
(b,3)
(s,1)
(a,3)
(c,3)
扩展:上面我们说了使用spark-submit提交任务的时候可以使用--packagers
,--jars
但是这并不是一个很好的方法,我们可以使用maven编译的时候加上需要的类,做法如下:
1.在pom.xml添加
<plugin>
<artifactId>maven-assembly-pluginartifactId>
<configuration>
<archive>
<manifest>
<mainClass>mainClass>
manifest>
archive>
<descriptorRefs>
<descriptorRef>jar-with-dependenciesdescriptorRef>
descriptorRefs>
configuration>
plugin>
Dependency Scope
在POM 4中,dependency中还引入了scope,它主要管理依赖的部署。目前可以使用5个值:
依赖范围控制哪些依赖在哪些classpath 中可用,哪些依赖包含在一个应用中。让我们详细看一下每一种范围:
属性 | 含义 |
---|---|
compile (编译范围) | compile是默认的范围;如果没有提供一个范围,那该依赖的范围就是编译范围。编译范围依赖在所有的classpath 中可用,同时它们也会被打包。 |
provided (已提供范围) | provided 依赖只有在当JDK 或者一个容器已提供该依赖之后才使用。例如, 如果你开发了一个web 应用,你可能在编译 classpath 中需要可用的Servlet API 来编译一个servlet,但是你不会想要在打包好的WAR 中包含这个Servlet API;这个Servlet API JAR 由你的应用服务器或者servlet 容器提供。已提供范围的依赖在编译classpath (不是运行时)可用。它们不是传递性的,也不会被打包。 |
runtime (运行时范围) | runtime 依赖在运行和测试系统的时候需要,但在编译的时候不需要。比如,你可能在编译的时候只需要JDBC API JAR,而只有在运行的时候才需要JDBC驱动实现。 |
test (测试范围) | test范围依赖 在一般的编译和运行时都不需要,它们只有在测试编译和测试运行阶段可用。 |
system (系统范围) | system范围依赖与provided 类似,但是你必须显式的提供一个对于本地系统中JAR 文件的路径。这么做是为了允许基于本地对象编译,而这些对象是系统类库的一部分。这样的构件应该是一直可用的,Maven 也不会在仓库中去寻找它。如果你将一个依赖范围设置成系统范围,你必须同时提供一个 systemPath 元素。注意该范围是不推荐使用的(你应该一直尽量去从公共或定制的 Maven 仓库中引用依赖)。 |
所以我们可以在编译的时候把FlumeUtils这个类加进去:
使用mvn assembly:assembly
命令进行编译