kafka的OffsetOutOfRangeException

参考文章kafka.common.OffsetOutOfRangeException 问题处理
这几天折腾spark的kafka的低阶API createDirectStream的一些总结。

问题描述

国庆前启动spark streaming任务去消费了kafka,后来由于其他原因停止了,放假回来后,重启spark任务后,报kafka.common.OffsetOutOfRangeException,期初我以为是ZK重启造成的就换了个group.id正常了,今天看到一篇文章才知道真实的原因

kafka会定时清理日志

当我们的任务开始的时候,如果之前消费过某个topic,那么这个topic会在zk上设置offset,我们一般会去获取这个offset来继续从上次结束的地方继续消费,但是kafka定时清理日志的功能,比如定时一天一清理,那么如果你的offset是前天消费的offset,那么这个时候你再去消费,自然而然的你的offset肯定已经不在有效范围内,所以就报OffsetOutOfRangeException

解决方法

也很简单,就是去判断一下zk中的offset是否小于topic最小的offset,如果小于的话,就把最小的offset设置到zk中

代码

def setOrUpdateOffsets(implicit topics: Set[String], kc: KafkaCluster): Unit = {
    topics.foreach(topic => {
      println("current topic:" + topic)
      val groupId = Config.kafkaConf.getOrElse("group.id", "")
      var hasConsumed = true
      val kafkaPartitionsE = kc.getPartitions(Set(topic))
      if (kafkaPartitionsE.isLeft) throw new SparkException("get kafka partition failed:")
      val kafkaPartitions = kafkaPartitionsE.right.get
      val consumerOffsetsE = kc.getConsumerOffsets(groupId, kafkaPartitions)
      if (consumerOffsetsE.isLeft) hasConsumed = false
      if (hasConsumed) {
        //如果有消费过,有两种可能,如果streaming程序执行的时候出现kafka.common.OffsetOutOfRangeException,说明zk上保存的offsets已经过时了,即kafka的定时清理策略已经将包含该offsets的文件删除。
        //针对这种情况,只要判断一下zk上的consumerOffsets和leaderEarliestOffsets的大小,如果consumerOffsets比leaderEarliestOffsets还小的话,说明是过时的offsets,这时把leaderEarliestOffsets更新为consumerOffsets
        val leaderEarliestOffsets = kc.getEarliestLeaderOffsets(kafkaPartitions).right.get
        println(leaderEarliestOffsets)
        val consumerOffsets = consumerOffsetsE.right.get
        val flag = consumerOffsets.forall {
          case (tp, n) => n < leaderEarliestOffsets(tp).offset
        }
        if (flag) {
          println("consumer group:" + groupId + " offsets已经过时,更新为leaderEarliestOffsets")
          val offsets = leaderEarliestOffsets.map {
            case (tp, offset) => (tp, offset.offset)
          }
          kc.setConsumerOffsets(groupId, offsets)
        }
        else {
          println("consumer group:" + groupId + " offsets正常,无需更新")
        }
      }
      else {
        //如果没有被消费过,则从最新的offset开始消费。
        val leaderLatestOffsets = kc.getLatestLeaderOffsets(kafkaPartitions).right.get
        println(leaderLatestOffsets)
        println("consumer group:" + groupId + " 还未消费过,更新为leaderLatestOffsets")
        val offsets = leaderLatestOffsets.map {
          case (tp, offset) => (tp, offset.offset)
        }
        kc.setConsumerOffsets(groupId, offsets)
      }
    })
  }

你可能感兴趣的:(spark)