1 概述
本文对于Kafka对接Spark Streaming进行学习。
官方地址,但是需要注意的是:Kafka项目在版本0.8和0.10之间引入了新的消费者API,因此有两个独立的相应Spark Streaming软件包可用。所以使用的时候要注意版本的问题。如下图所示版本选择:
作者使用版本介绍(伪分布式):
kafka_2.11-0.10.0.1.tgz
scala-2.11.8.tgz
zookeeper-3.4.5-cdh5.7.0.tar.gz
kafka下载地址
scala下载地址
zookeeper下载地址
2 安装部署
- zookeeper-3.4.5-cdh5.7.0.tar.gz
1.下载解压zookeeper-3.4.5-cdh5.7.0.tar.gz
tar -xzvf zookeeper-3.4.5-cdh5.7.0.tar.gz
2.修改权限
chown -R hadoop;hadoop zookeeper-3.4.5-cdh5.7.0
3.修改配置
cp zoo_sample.cfg zoo.cfg
vi zoo.cfg
修改目录
dataDir=/opt/software/zookeeper-3.4.5-cdh5.7.0/data
mkdir data
4.启动
./zkServer.sh start
5.查看状态
./zkServer.sh status
- kafka_2.11-0.10.0.1.tgz
1.解压
tar -xzvf kafka_2.11-0.10.0.1.tgz
2.创建日志目录
cd kafka
mkdir logs
3.修改配置文件
vi server.properties
port=9092
host.name=192.168.137.141
log.dirs=/opt/software/kafka/logs
4.后台启动
./kafka-server-start.sh -daemon /opt/software/kafka_2.11-0.10.0.1/config/server.properties
- 检验kafka是否安装成功
创建topic
./kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic streaming_topic
创建生产者
[hadoop@hadoop bin]$ ./kafka-console-producer.sh --broker-list localhost:9092 --topic streaming_topic
创建消费者
./kafka-console-consumer.sh --zookeeper localhost:2181 --topic streaming_topic
在生产者端发送消息看看消费者能否接受到
3 Spark Streaming + Kafka
Kafka 0.10的Spark Streaming集成设计与0.8 Direct Stream方法类似。 它提供了简单的并行性,Kafka分区和Spark分区之间的1:1对应关系,以及对偏移量和元数据的访问。 但是,由于较新的集成使用新的Kafka消费者API而不是简单的API,因此在使用方面存在显着差异。 此版本的集成标记为实验性,因此API可能会发生变化。
- maven中添加的gav
groupId = org.apache.spark
artifactId = spark-streaming-kafka-0-10_2.11
version = 2.3.0
- 生产者
./kafka-console-producer.sh --broker-list 192.168.137.130:9092 --topic streaming_topic
- 消费者
./kafka-console-consumer.sh --zookeeper 192.168.137.130:2181 --topic streaming_topic
- 代码
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.SparkConf
import org.apache.spark.streaming.kafka010.{ConsumerStrategies, KafkaUtils, LocationStrategies}
object KafkaStreaming {
def main(args: Array[String]): Unit = {
//创建StreamingContext
val sparkConf=new SparkConf().setAppName("KafkaStreaming").setMaster("local[2]")
val ssc=new StreamingContext(sparkConf,Seconds(10))
//准备参数
val kafkaParams = Map[String, Object](
"bootstrap.servers" -> "192.168.137.130:9092",
"key.deserializer" -> classOf[StringDeserializer],
"value.deserializer" -> classOf[StringDeserializer],
"group.id" -> "use_a_separate_group_id_for_each_stream",
//Kafka提供的有api,可以将offset提交到指定的kafkatopic或者选择checkpoint的方式,下面会说
"auto.offset.reset" -> "latest",
"enable.auto.commit" -> (false: java.lang.Boolean)
)
//上面通过Kafka提供的有api设置过offset
// ssc.checkpoint("/flume_streaming/data")
//topics(注意这里是复数哦)
val topics = Array("streaming_topic")
//通过KafkaUtils放回一个DStream
val stream = KafkaUtils.createDirectStream[String, String](
ssc,
LocationStrategies.PreferConsistent,
ConsumerStrategies.Subscribe[String, String](topics, kafkaParams)
)
//stream.map(record => (record.key, record.value)).print()
//wc
val lines=stream.map(_.value)
val word=lines.flatMap(_.split(",")).map(x=>(x,1)).reduceByKey(_+_).print
ssc.start()
ssc.awaitTermination()
}
}
- 实验
在生产者中输入:
a,a,a,b,b,b,c,c,c,d,d,e,e,r,f,g
结果:
(d,2)
(b,3)
(f,1)
(r,1)
(e,2)
(a,3)
(g,1)
(c,3)
**注意:***kafka的参数,请参考kafka官网。如果,你的spark批次时间超过了kafka的心跳时间(30s),需要增加heartbeat.interval.ms和session.timeout.ms。例如,批处理时间是5min,那么就需要调整group.max.session.timeout.ms。注意,例子中是将enable.auto.commit设置为了false,enable.auto.commit参数值是true,含义是当数据被消费完之后会,如果spark streaming的程序由于某种原因停止之后再启动,下次不会重复消费之前消费过的数据。
4 本地策略(LocationStrategies)
新版本的消费者API会预取消息入buffer。因此,为了提升性能,在Executor端缓存消费者(而不是每个批次重新创建)是非常有必要的,优先调度那些分区到已经有了合适消费者主机上。
在很多情况下,你需要像上文一样使用LocationStrategies.PreferConsistent,这个参数会将分区尽量均匀地分配到所有的可以Executor上去。如果,你的Executor和kafka broker在同一台机器上,可以用PreferBrokers,这将优先将分区调度到kafka分区leader所在的主机上。最后,分区间负荷有明显的倾斜,可以用PreferFixed。这个允许你指定一个明确的分区到主机的映射(没有指定的分区将会使用连续的地址)。
消费者缓存的数目默认最大值是64。如果你希望处理超过(64*excutor数目)kafka分区,spark.streaming.kafka.consumer.cache.maxCapacity这个参数可以帮助你修改这个值。
如果你想禁止kafka消费者缓存,可以将spark.streaming.kafka.consumer.cache.enabled修改为false。禁止缓存缓存可能需要解决SPARK-19185描述的问题。一旦这个bug解决,这个属性将会在后期的spark版本中移除。
Cache是按照topicpartition和groupid进行分组的,所以每次调用creaDirectStream的时候要单独设置group.id。
5 存储偏移
Kafka在有可能存在任务失败的情况下的从消息传输语义(至少一次,最多一次,恰好一次)是取决于何时存储offset。Spark输出操作是至少一次传输语义。所以,如果你想实现仅仅一次的消费语义,你必须要么在密等输出后存储offset,要么就是offset的存储和结果输出是一次事务。
1, Checkpoint
如果使能了Checkpoint,offset被存储到Checkpoint。这个虽然很容易做到,但是也有一些缺点。由于会多次输出结果,所以结果输出必须是满足幂等性。同时事务性不可选。另外,如果代码变更,你是不可以从Checkpoint恢复的。针对代码升级更新操作,你可以同时运行你的新任务和旧任务(因为你的输出结果是幂等性)。对于以外的故障,并且同时代码变更了,肯定会丢失数据的,除非另有方式来识别启动消费的偏移。
2, Kafka自身
Kafka提供的有api,可以将offset提交到指定的kafkatopic。默认情况下,新的消费者会周期性的自动提交offset到kafka。但是有些情况下,这也会有些问题,因为消息可能已经被消费者从kafka拉去出来,但是spark还没处理,这种情况下会导致一些错误。这也是为什么例子中stream将enable.auto.commit设置为了false。然而在已经提交spark输出结果之后,你可以手动提交偏移到kafka。相对于Checkpoint,offset存储到kafka的好处是:kafka既是一个容错的存储系统,也是可以避免代码变更带来的麻烦。提交offset到kafka和结果输出也不是一次事务,所以也要求你的输出结果是满足幂等性。