我们从Spark的官方文档可以知道,维护Spark内部维护Kafka便宜了信息是存储在HasOffsetRanges类的offsetRanges中,我们可以在Spark Streaming程序里面获取这些信息:
- val offsetsList = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
- val messages = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder](ssc, kafkaParams, topicsSet)
- messages.foreachRDD(rdd => {
- val offsetsList = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
- val kc = new KafkaCluster(kafkaParams)
- for (offsets < - offsetsList) {
- val topicAndPartition = TopicAndPartition("test-topic", offsets.partition)
- val o = kc.setConsumerOffsets(args(0), Map((topicAndPartition, offsets.untilOffset)))
- if (o.isLeft) {
- println(s"Error updating the offset to Kafka cluster: ${o.left.get}")
- }
- }
- })
KafkaCluster类用于建立和Kafka集群的链接相关的操作工具类,我们可以对Kafka中Topic的每个分区设置其相应的偏移量Map((topicAndPartition, offsets.untilOffset)),然后调用KafkaCluster类的setConsumerOffsets方法去更新Zookeeper里面的信息,这样我们就可以更新Kafka的偏移量,最后我们就可以通过KafkaOffsetMonitor之类软件去监控Kafka中相应Topic的消费信息,下图是KafkaOffsetMonitor的监控情况:
从图中我们可以看到KafkaOffsetMonitor监控软件已经可以监控到Kafka相关分区的消费情况,这对监控我们整个Spark Streaming程序来非常重要,因为我们可以任意时刻了解Spark读取速度。另外,KafkaCluster工具类的完整代码如下:
- package org.apache.spark.streaming.kafka
- import kafka.api.OffsetCommitRequest
- import kafka.common.{ErrorMapping, OffsetMetadataAndError, TopicAndPartition}
- import kafka.consumer.SimpleConsumer
- import org.apache.spark.SparkException
- import org.apache.spark.streaming.kafka.KafkaCluster.SimpleConsumerConfig
- import scala.collection.mutable.ArrayBuffer
- import scala.util.Random
- import scala.util.control.NonFatal
- class KafkaCluster(val kafkaParams: Map[String, String]) extends Serializable {
- type Err = ArrayBuffer[Throwable]
- @transient private var _config: SimpleConsumerConfig = null
- def config: SimpleConsumerConfig = this.synchronized {
- if (_config == null) {
- _config = SimpleConsumerConfig(kafkaParams)
- }
- _config
- }
- def setConsumerOffsets(groupId: String,
- offsets: Map[TopicAndPartition, Long]
- ): Either[Err, Map[TopicAndPartition, Short]] = {
- setConsumerOffsetMetadata(groupId, offsets.map { kv =>
- kv._1 -> OffsetMetadataAndError(kv._2)
- })
- }
- def setConsumerOffsetMetadata(groupId: String,
- metadata: Map[TopicAndPartition, OffsetMetadataAndError]
- ): Either[Err, Map[TopicAndPartition, Short]] = {
- var result = Map[TopicAndPartition, Short]()
- val req = OffsetCommitRequest(groupId, metadata)
- val errs = new Err
- val topicAndPartitions = metadata.keySet
- withBrokers(Random.shuffle(config.seedBrokers), errs) { consumer =>
- val resp = consumer.commitOffsets(req)
- val respMap = resp.requestInfo
- val needed = topicAndPartitions.diff(result.keySet)
- needed.foreach { tp: TopicAndPartition =>
- respMap.get(tp).foreach { err: Short =>
- if (err == ErrorMapping.NoError) {
- result += tp -> err
- } else {
- errs.append(ErrorMapping.exceptionFor(err))
- }
- }
- }
- if (result.keys.size == topicAndPartitions.size) {
- return Right(result)
- }
- }
- val missing = topicAndPartitions.diff(result.keySet)
- errs.append(new SparkException(s"Couldn't set offsets for ${missing}"))
- Left(errs)
- }
- private def withBrokers(brokers: Iterable[(String, Int)], errs: Err)
- (fn: SimpleConsumer => Any): Unit = {
- brokers.foreach { hp =>
- var consumer: SimpleConsumer = null
- try {
- consumer = connect(hp._1, hp._2)
- fn(consumer)
- } catch {
- case NonFatal(e) =>
- errs.append(e)
- } finally {
- if (consumer != null) {
- consumer.close()
- }
- }
- }
- }
- def connect(host: String, port: Int): SimpleConsumer =
- new SimpleConsumer(host, port, config.socketTimeoutMs,
- config.socketReceiveBufferBytes, config.clientId)
- }