kafka作为一个实时的分布式消息队列,实时的生产和消费消息,这里我们可以利用SparkStreaming实时地读取kafka中的数据,然后进行相关计算。
在Spark1.3版本后,KafkaUtils里面提供了两个创建dstream的方法,一种为KafkaUtils.createDstream,另一种为KafkaUtils.createDirectStream。
官方文档说明
http://spark.apache.org/docs/2.2.0/streaming-kafka-integration.html
1 下载kafka安装压缩包
http://archive.apache.org/dist/kafka/
2 上传压缩包并解压
将kafka的安装包上传到第一台服务器的/export/softwares路径下面去,然后解压到/export/servers
这里统一使用 kafka_2.11-1.0.0.tgz 这个版本
3 修改kafka配置文件
第一台机器修改kafka配置文件server.properties
broker.id=0
num.network.threads=3
num.io.threads=8
socket.send.buffer.bytes=102400
socket.receive.buffer.bytes=102400
socket.request.max.bytes=104857600
log.dirs=/export/servers/kafka_2.11-1.0.0/logs
num.partitions=2
num.recovery.threads.per.data.dir=1
offsets.topic.replication.factor=1
transaction.state.log.replication.factor=1
transaction.state.log.min.isr=1
log.flush.interval.messages=10000
log.flush.interval.ms=1000
log.retention.hours=168
log.segment.bytes=1073741824
log.retention.check.interval.ms=300000
zookeeper.connect=node01:2181,node02:2181,node03:2181
zookeeper.connection.timeout.ms=6000
group.initial.rebalance.delay.ms=0
delete.topic.enable=true
host.name=node01
第二台机器修改kafka配置文件server.properties
broker.id=1
num.network.threads=3
num.io.threads=8
socket.send.buffer.bytes=102400
socket.receive.buffer.bytes=102400
socket.request.max.bytes=104857600
log.dirs=/export/servers/kafka_2.11-1.0.0/logs
num.partitions=2
num.recovery.threads.per.data.dir=1
offsets.topic.replication.factor=1
transaction.state.log.replication.factor=1
transaction.state.log.min.isr=1
log.flush.interval.messages=10000
log.flush.interval.ms=1000
log.retention.hours=168
log.segment.bytes=1073741824
log.retention.check.interval.ms=300000
zookeeper.connect=node01:2181,node02:2181,node03:2181
zookeeper.connection.timeout.ms=6000
group.initial.rebalance.delay.ms=0
delete.topic.enable=true
host.name=node02
第三台机器修改kafka配置文件server.properties
broker.id=2
num.network.threads=3
num.io.threads=8
socket.send.buffer.bytes=102400
socket.receive.buffer.bytes=102400
socket.request.max.bytes=104857600
log.dirs=/export/servers/kafka_2.11-1.0.0/logs
num.partitions=2
num.recovery.threads.per.data.dir=1
offsets.topic.replication.factor=1
transaction.state.log.replication.factor=1
transaction.state.log.min.isr=1
log.flush.interval.messages=10000
log.flush.interval.ms=1000
log.retention.hours=168
log.segment.bytes=1073741824
log.retention.check.interval.ms=300000
zookeeper.connect=node01:2181,node02:2181,node03:2181
zookeeper.connection.timeout.ms=6000
group.initial.rebalance.delay.ms=0
delete.topic.enable=true
host.name=node03
4启动kafka集群
三台机器启动kafka服务
bin/kafka-server-start.sh config/server.properties > /dev/null 2>&1 &
KafkaUtils.createDstream(ssc, [zk], [group id], [per-topic,partitions] ) 使用了receivers接收器来接收数据,利用的是Kafka高层次的消费者api,对于所有的receivers接收到的数据将会保存在Spark executors中,然后通过Spark Streaming启动job来处理这些数据,默认会丢失,可启用WAL日志,它同步将接受到数据保存到分布式文件系统上比如HDFS。 所以数据在出错的情况下可以恢复出来 。
A、创建一个receiver接收器来对kafka进行定时拉取数据,这里产生的dstream中rdd分区和kafka的topic分区不是一个概念,故如果增加特定topic的分区数仅仅是增加一个receiver中消费topic的线程数,并没有增加spark的并行处理的数据量。
B、对于不同的group和topic可以使用多个receivers创建不同的DStream
C、如果启用了WAL(spark.streaming.receiver.writeAheadLog.enable=true)
同时需要设置存储级别(默认StorageLevel.MEMORY_AND_DISK_SER_2),
第一步:导入jar包
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming-kafka-0-8_2.11</artifactId>
<version>2.2.0</version>
</dependency>
第二步:创建kafka的topic
node01服务器执行以下命令创建kafka的topic sparkafka
cd /export/servers/kafka_2.11-1.0.0/
bin/kafka-topics.sh --create --partitions 3 --replication-factor 2 --topic sparkafka --zookeeper node01:2181,node02:2181,node03:2181
第三步:使用脚本启动kafka生产者
node01服务器执行以下命令通过脚本模拟kafka生产者
cd /export/servers/kafka_2.11-1.0.0/
bin/kafka-console-producer.sh --broker-list node01:9092,node02:9092,node03:9092 --topic sparkafka
第四步:开发SparkStreaming对接kafka代码
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.kafka.KafkaUtils
import scala.collection.immutable
object StreamKafkaReceiver {
def main(args: Array[String]): Unit = {
//1、创建sparkConf
val sparkConf: SparkConf = new SparkConf()
.setAppName("SparkStreamingKafka_Receiver")
.setMaster("local[4]")
.set("spark.streaming.receiver.writeAheadLog.enable", "true") //开启wal预写日志,保存数据源的可靠性
//2、创建sparkContext
val sc = new SparkContext(sparkConf)
sc.setLogLevel("WARN")
//3、创建StreamingContext
val ssc = new StreamingContext(sc, Seconds(5))
//设置checkpoint
ssc.checkpoint("./Kafka_Receiver")
//4、定义zk地址
val zkQuorum = "node01:2181,node02:2181,node03:2181"
//5、定义消费者组
val groupId = "sparkafka_group"
//6、定义topic相关信息 Map[String, Int]
// 指定消费的topic的名称和消费topic的线程数
val topics = Map("sparkafka" -> 3)
//7、通过KafkaUtils.createDStream对接kafka
//这个时候相当于同时开启3个receiver接受数据
val receiverDstream: immutable.IndexedSeq[ReceiverInputDStream[(String, String)]] = (1 to 3).map(x => {
val stream: ReceiverInputDStream[(String, String)] = KafkaUtils.createStream(ssc, zkQuorum, groupId, topics)
stream
}
)
//使用ssc.union方法合并所有的receiver中的数据
val unionDStream: DStream[(String, String)] = ssc.union(receiverDstream)
//8、获取topic中的数据
val topicData: DStream[String] = unionDStream.map(_._2)
//9、切分每一行,每个单词计为1
val wordAndOne: DStream[(String, Int)] = topicData.flatMap(_.split(" ")).map((_, 1))
//10、相同单词出现的次数累加
val result: DStream[(String, Int)] = wordAndOne.reduceByKey(_ + _)
//11、打印输出
result.print()
//开启计算
ssc.start()
ssc.awaitTermination()
}
}
使用kafka的低阶API进行消费,消费数据的offset全部维护在kafka当中的一个topic当中,会自动提交offset,同时也可以手动提交维护offset更加安全
import kafka.serializer.StringDecoder
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.dstream.{DStream, InputDStream}
import org.apache.spark.streaming.kafka.KafkaUtils
//todo:利用sparkStreaming对接kafka实现单词计数----采用Direct(低级API)
object SparkStreamingKafka_Direct {
def main(args: Array[String]): Unit = {
//1、创建sparkConf
val sparkConf: SparkConf = new SparkConf()
.setAppName("SparkStreamingKafka_Direct")
.setMaster("local[2]")
//2、创建sparkContext
val sc = new SparkContext(sparkConf)
sc.setLogLevel("WARN")
//3、创建StreamingContext
val ssc = new StreamingContext(sc, Seconds(5))
ssc.checkpoint("./Kafka_Direct")
//4、配置kafka相关参数
val kafkaParams = Map("metadata.broker.list" -> "node01:9092,node02:9092,node03:9092", "group.id" -> "Kafka_Direct")
//5、定义topic
val topics = Set("sparkafka")
//6、通过 KafkaUtils.createDirectStream接受kafka数据,这里采用是kafka低级api偏移量不受zk管理
val dstream: InputDStream[(String, String)] = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder](ssc, kafkaParams, topics)
//7、获取kafka中topic中的数据
val topicData: DStream[String] = dstream.map(_._2)
//8、切分每一行,每个单词计为1
val wordAndOne: DStream[(String, Int)] = topicData.flatMap(_.split(" ")).map((_, 1))
//9、相同单词出现的次数累加
val result: DStream[(String, Int)] = wordAndOne.reduceByKey(_ + _)
//10、打印输出
result.print()
//开启计算
ssc.start()
ssc.awaitTermination()
}
}
手动提交offset,进行offset的管理维护,保证数据不会丢失,推荐使用
第一步:导入jar包
<!-- <dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming-kafka-0-8_2.11</artifactId>
<version>2.2.0</version>
</dependency>-->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming-kafka-0-10_2.11</artifactId>
<version>2.2.0</version>
</dependency>
第二步:整合代码开发
手动提交offset管理,保证数据消费exactly once
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.streaming.kafka010._
import org.apache.spark.{SparkConf, SparkContext, TaskContext}
import org.apache.spark.streaming.{Seconds, StreamingContext}
object StreamingKafka1 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[4]").setAppName("NetworkWordCount")
val context = new SparkContext(conf)
context.setLogLevel("WARN")
val ssc = new StreamingContext(context, Seconds(1))
//创建topic
val brokers= "node01:9092,node02:9092,node03:9092"
val sourcetopic="sparkafka";
//创建消费者组
var group="sparkafkaGroup"
//消费者配置
val kafkaParam = Map(
"bootstrap.servers" -> brokers,//用于初始化链接到集群的地址
"key.deserializer" -> classOf[StringDeserializer],
"value.deserializer" -> classOf[StringDeserializer],
//用于标识这个消费者属于哪个消费团体
"group.id" -> group,
//如果没有初始化偏移量或者当前的偏移量不存在任何服务器上,可以使用这个配置属性
//可以使用这个配置,latest自动重置偏移量为最新的偏移量
"auto.offset.reset" -> "latest",
//如果是true,则这个消费者的偏移量会在后台自动提交
"enable.auto.commit" -> (false: java.lang.Boolean)
);
var stream = KafkaUtils.createDirectStream[String,String](ssc,LocationStrategies.PreferConsistent,ConsumerStrategies.Subscribe[String,String](Array("sparkafka"),kafkaParam))
//循环遍历每个RDD当中的数据
stream.foreachRDD(f =>{
//判断如果rdd当中有数据,就进行处理,没有数据就不用处理
if(f.count() > 0){
println("接收kafka当中的数据")
//每个分区当中的数据进行循环遍历,遍历每个分区当中每一行的数据
f.foreach(f =>{
//获取kafka当中的数据内容
val kafkaValue: String = f.value()
println(kafkaValue)
})
//打印offset的信息
val offsetRanges: Array[OffsetRange] = f.asInstanceOf[HasOffsetRanges].offsetRanges
f.foreachPartition { iter =>
val o: OffsetRange = offsetRanges(TaskContext.get.partitionId)
println(s"${o.topic} ${o.partition} ${o.fromOffset} ${o.untilOffset}")
}
println("=============================")
// 等输出操作完成后提交offset
stream.asInstanceOf[CanCommitOffsets].commitAsync(offsetRanges)
}
})
ssc.start()
ssc.awaitTermination()
}
}