一、Streaming与Flume的联调
Spark 2.2.0 对应于 Flume 1.6.0
两种模式:
1. Flume-style push-based approach:
Flume推送数据給Streaming
Streaming的receiver作为Flume的Avro agent
Spark workers应该跑在Flume这台机器上
Streaming先启动,receiver监听Flume push data的端口
实现:
写flume配置文件:
netcat source -> memory channel -> avro sink
IDEA开发:
添加Spark-flume依赖
对应的API是FlumeUtils
开发代码:
importorg.apache.spark.SparkConfimportorg.apache.spark.streaming.flume.FlumeUtilsimportorg.apache.spark.streaming.{Seconds, StreamingContext}/** Spark Streaming整合Flume的第一种方式
**/object FlumePushWordCount {
def main(args: Array[String]): Unit={//外部传入参数
if (args.length != 2) {
System.out.println("Usage: FlumePushWordCount ")
System.exit(1)
}
val Array(hostname, port)= args //外部args数组
val sparkConf= new SparkConf().setMaster("local[2]").setAppName("FlumePushWordCount")
val ssc= new StreamingContext(sparkConf, Seconds(5))//选择输入ssc的createStream方法,生成一个InputDStream
val flumeStream =FlumeUtils.createStream(ssc, hostname, port.toInt)//由于flume的内容有head有body, 需要先把内容拿出来, 并去掉空值
flumeStream.map(x => newString(x.event.getBody.array()).trim)
.flatMap(x=> x.split(" ")).map(x => (x, 1)).reduceByKey(_+_).print()
ssc.start()
ssc.awaitTermination()
}
}
注意:为了不hard-core,选择外部传入hostname和port
在IDEA测试时,可以在
里面的program argument输入运行参数
在本地测试时:
先启动Streaming作业,然后启动flume agent,最后通过telnet输入数据,观察IDEA的控制台输出
在服务器测试时:
submit时一定要把maven依赖中在--packages加上,自动会在网络上下载依赖
当不能下载时,需要--jars才能把预先下载好的jar包加上
2. Pull-based approach using a custom sink:
Streaming拉数据
Flume推送的数据先放到sink缓冲区
Streaming使用一个reliable flume receiver,确保了数据的接收和备份
可靠性更高,支持容错,生产上面常用
一台机器运行Flume agent,Spark集群其他机器可访问这台机器的custom sink
实现:
Flume配置:
使用相关jars包,配置依赖:(参考Spark官网)
sink是一个独特的type
IDEA开发:
对应上面Flume的依赖,使用的是createPollStream,区别于第一种模式
其他地方都一样,体现了Spark代码的复用性
本地测试:
先启动flume!!后启动Streaming作业
二、Streaming与Kafka的联调
Spark2.2.0对应于Kafka 0.8.2.1或更新(本次使用的是0.9.0.0)
两种模式:
1. Receiver-based approach
使用Kafka高级用户API
为了确保零数据丢失,需要用到Write Ahead Logs(出现于Spark 1.2)
同步地保存接收到的数据到日志当中,出错时可以恢复(容错机制)
这是传统的方式,在ZK server中消费数据
用KafkaUtils和Streaming对接,一样需要加入kafka的各种依赖(见官网)
使用的API是createStream
注意:
此处的topic分区和RDD的分区不同概念
多个Kafka DStream可以并行接收
用write ahead logs时需要配置StorageLevel.MEMORY_AND_DISK_SER
准备工作:
启动ZK server
启动kafka
./bin/kafka-server-start.sh -daemon ./config/server.properties
创建topic
./bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic kafka_streaming_topic
测试topic能否正确生产和消费
kafka-console-producer.sh --broker-list localhost:9092 --topic kafka_streaming_topic
kafka-console-consumer.sh --zookeeper localhost:2181 --topic kafka_streaming_topic
IDEA代码:
importorg.apache.spark.SparkConfimportorg.apache.spark.streaming.kafka.KafkaUtilsimportorg.apache.spark.streaming.{Seconds, StreamingContext}/** SparkStreaming对接Kafka其中的Receiver-based方式
**/object KafkaReceiverWordCount {
def main(args: Array[String]): Unit={if (args.length != 4) {
System.out.println("Usage: KafkaReceiverWordCount ")
System.exit(1)
}
val Array(zkQuorum, group, topics, numThreads)=args
val sparkConf= new SparkConf().setMaster("local[2]").setAppName("KafkaReceiverWordCount")
val ssc= new StreamingContext(sparkConf, Seconds(5))//createStream需要传入的其中一个参数是一个Map,就是topics对应的线程数
val topicsMap = topics.split(",").map((_, numThreads.toInt)).toMap
val message=KafkaUtils.createStream(ssc, zkQuorum, group, topicsMap)//一定要取Stream的第二位才是数据,可以print出来看看,在实际生产中只是更改这一行的业务逻辑!!!
message.map(_._2).flatMap(_.split(",")).map((_, 1)).reduceByKey(_+_).print()
ssc.start()
ssc.awaitTermination()
}
}
本地测试/服务器测试:
从IDEA中输入参数,即可看到结果
从服务器测试也是打包submit就行,看web UI的时候留意验证receiver是占有一个Job的,证实了前面的理论
2. Direct Approach
No receiver!!!
从Spark 1.3 版本开始有
没有了Receiver,而是周期性地检测Kafka的offset,用了kafka simple consumer API
优点:
简化了并行度,不需要创建多个input stream
性能更好,达到零数据丢失,且不需要保存副本于write ahead logs中
一次语义Exactly-once semantics
缺点:不能在zookeeper中更新offset,但可以自己设置让其更新
使用的API是createDirectStream
准备工作和上面一样。
IDEA代码:
importorg.apache.spark.SparkConfimportorg.apache.spark.streaming.kafka.KafkaUtilsimportorg.apache.spark.streaming.{Seconds, StreamingContext}/** SparkStreaming对接Kafka其中的Direct方式
**/object KafkaDirectWordCount {
def main(args: Array[String]): Unit={if (args.length != 4) {
System.out.println("Usage: KafkaReceiverWordCount ")
System.exit(1)
}
val Array(brokers, topics)=args
val sparkConf= new SparkConf().setMaster("local[2]").setAppName("KafkaReceiverWordCount")
val ssc= new StreamingContext(sparkConf, Seconds(5))//createDirectStream需要传入kafkaParams和topicsSet
val kafkaParams = Map[String, String]("metadata.broker.list" ->brokers)
val topicsSet= topics.split(",").toSet
val message=KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder](
ssc, kafkaParams, topicsSet
)//一定要取Stream的第二位才是数据,可以print出来看看
message.map(_._2).flatMap(_.split(",")).map((_, 1)).reduceByKey(_+_).print()
ssc.start()
ssc.awaitTermination()
}
}
注意:StringDecoder有可能因为前面写Kafka java API时的包冲突而导入失败
在IDEA运行时报错:
这是由于之前在Kafka基础学习中我设置的kafka的依赖是0.9.0.0,和我们IDEA冲突,所以要把这一个依赖注释掉才能执行
调优时就是配置createDirectStream的参数嘛!!
三、Flume + Kafka + Spark Streaming常用流处理架构
实现的需求:实时(到现在为止)的日志访问统计操作
由于本人缺乏日志采集来源,故使用python语言来实现一个日志生成器,模拟生产环境中服务器不断生成日志的过程
本生成器产生的日志内容包括ip、time、url、status、referer
根据前面的知识,我们在实现的过程中有以下步骤:
1. Flume的选型,在本例中设为exec-memory-kafka
2. 打开kafka一个消费者,再启动flume读取日志生成器中的log文件,可看到kafka中成功读取到日志产生器的实时数据
3. 让Kafka接收到的数据传输到Spark Streaming当中,这样就可以在Spark对实时接收到的数据进行操作了
由于与前面一、二的操作基本一致,此处不再重复列出详细操作过程
下面直接进入Spark中对实时数据的操作:
分为数据清洗过程、统计功能实现过程两个步骤!其中统计功能的实现基本上和Spark SQL中的操作一致,这又体现了Spark的代码复用性,即能通用于多个框架中