Kafka如何维护offset详解

1、Direct 模式维护offset

  • 1.6版本时候可以用checkpoint来维护,也可以手动维护。2.3也可以。

1.设置checkpoint,使用 StreamingContext.getOrCreate()方式来维护 offset,但这种维护方式有个弊端就是,如果Driver死掉,重新启动去消费数据的时候,会把之前大概五分钟消费过的数据重新消费一边,就涉及到重复消费数据问题,那么自己本身就要对重复消费的数据做逻辑,比如添加数据库,去重之类的,还有就是这种方式,从Checkpoint恢复的Driver无法执行新逻辑 详解看文章SparkDriverHA。。。


  • 耐心阅读下面的文字
/**
  * SparkStreaming2.3版本 读取kafka 中数据 :
  *  1.采用了新的消费者api实现,类似于1.6中SparkStreaming 读取 kafka Direct模式。并行度 一样。
  *  2.因为采用了新的消费者api实现,所有相对于1.6的Direct模式【simple api实现】 ,api使用上有很大差别。未来这种api有可能继续变化
  *  3.kafka中有两个参数:
  *      heartbeat.interval.ms:这个值代表 kafka集群与消费者之间的心跳间隔时间,kafka 集群确保消费者保持连接的心跳通信时间间隔。这个时间默认是3s.
  *             这个值必须设置的比session.timeout.ms appropriately 小,一般设置不大于 session.timeout.ms appropriately 的1/3
  *      session.timeout.ms appropriately:
  *             这个值代表消费者与kafka之间的session 会话超时时间,如果在这个时间内,kafka 没有接收到消费者的心跳【heartbeat.interval.ms 控制】,
  *             那么kafka将移除当前的消费者。这个时间默认是10s。
  *             这个时间位于配置 group.min.session.timeout.ms【6s】 和 group.max.session.timeout.ms【300s】之间的一个参数,如果SparkSteaming 批次间隔时间大于5分钟,
  *             也就是大于300s,那么就要相应的调大group.max.session.timeout.ms 这个值。
  *  4.大多数情况下,SparkStreaming读取数据使用 LocationStrategies.PreferConsistent 这种策略,这种策略会将分区均匀的分布在集群的Executor之间。
  *    如果Executor在kafka 集群中的某些节点上,可以使用 LocationStrategies.PreferBrokers 这种策略,那么当前这个Executor 中的数据会来自当前broker节点。
  *    如果节点之间的分区有明显的分布不均,可以使用 LocationStrategies.PreferFixed 这种策略,可以通过一个map 指定将topic分区分布在哪些节点中。
  *
  *  5.新的消费者api 可以将kafka 中的消息预读取到缓存区中,默认大小为64k。默认缓存区在 Executor 中,加快处理数据速度。
  *     可以通过参数 spark.streaming.kafka.consumer.cache.maxCapacity 来增大,也可以通过spark.streaming.kafka.consumer.cache.enabled 设置成false 关闭缓存机制。
  *
  *  6.关于消费者offset
  *    1).如果设置了checkpoint ,那么offset 将会存储在checkpoint中。
  *     这种有缺点: 第一,当从checkpoint中恢复数据时,有可能造成重复的消费,需要我们写代码来保证数据的输出幂等。
  *                第二,当代码逻辑改变时,无法从checkpoint中来恢复offset.
  *    2).依靠kafka 来存储消费者offset,kafka 中有一个特殊的topic 来存储消费者offset。新的消费者api中,会定期自动提交offset。这种情况有可能也不是我们想要的,
  *       因为有可能消费者自动提交了offset,但是后期SparkStreaming 没有将接收来的数据及时处理保存。这里也就是为什么会在配置中将enable.auto.commit 设置成false的原因。
  *       这种消费模式也称最多消费一次,默认sparkStreaming 拉取到数据之后就可以更新offset,(不会造成重复消费问题)无论是否消费成功。自动提交offset的频率由参数auto.commit.interval.ms 决定,默认5s。
  *       如果我们能保证完全处理完业务之后,(如果Execute没处理完就提交offset 是不允许的).可以后期异步的手动提交消费者offset.
  *
  *    3).自己存储offset,这样在处理逻辑时,保证数据处理的事务,如果处理数据失败,就不保存offset,处理数据成功则保存offset.这样可以做到精准的处理一次处理数据。
  *
  */
  • 异步提交offset代码
  • 如果我们能保证完全处理完业务之后,(如果Execute没处理完就提交offset 是不允许的).可以后期异步的手动提交消费者offset.
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
    conf.setAppName("Direct")
    conf.setMaster("local")
    var ssc: StreamingContext = new StreamingContext(conf, Durations.seconds(5))
    //指定Topic的一些信息
    val map=Map(
      "bootstrap.servers" -> "hadoop110:9092,hadoop111:9092,hadoop112:9092",
      //key的反序列化 类型
      "key.deserializer" -> classOf[StringDeserializer],
      //value的反序列化类型
      "value.deserializer" -> classOf[StringDeserializer],
      "group.id" -> "my----",
      /**
        * 当没有初始的offset,或者当前的offset不存在,如何处理数据
        *  earliest :自动重置偏移量为最小偏移量
        *  latest:自动重置偏移量为最大偏移量【默认】
        *  none:没有找到以前的offset,抛出异常
        */
      "auto.offset.reset" -> "earliest",
      /**
        * 当设置 enable.auto.commit为false时,不会自动向kafka中保存消费者offset.需要异步的处理完数据之后手动提交
        */
      "enable.auto.commit" -> (false: java.lang.Boolean)//默认是true
    )

    val topicArr=Array("new_topic")
    val stream: InputDStream[ConsumerRecord[Nothing, Nothing]] = KafkaUtils.createDirectStream(ssc,
      PreferConsistent,
      Subscribe(topicArr, map))
    val transStrem: DStream[String] = stream.map(record => {
      val key_value = (record.key, record.value)
      println("receive message key = "+key_value._1)
      println("receive message value = "+key_value._2)
      key_value._2
    })
    val wordsDS: DStream[String] = transStrem.flatMap(line=>{line.split(" ")})
    val result: DStream[(String, Int)] = wordsDS.map((_,1)).reduceByKey(_+_)
    result.print()

    /**
      * 以上业务处理完成之后,异步的提交消费者offset,这里将 enable.auto.commit 设置成false,就是使用kafka 自己来管理消费者offset
      * 注意这里,获取 offsetRanges: Array[OffsetRange] 每一批次topic 中的offset时,必须从 源头读取过来的 stream中获取,不能从经过stream转换之后的DStream中获取。
      * offsetRanges: Array[OffsetRange] 中放的是消费那个kafka,那个topic,处理到那条消息了
      */
    stream.foreachRDD { rdd =>
      val offsetRanges: Array[OffsetRange] = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
      // some time later, after outputs have completed
      stream.asInstanceOf[CanCommitOffsets].commitAsync(offsetRanges)
    }
    ssc.start()
    ssc.awaitTermination()
    ssc.stop()
  }

在这里插入图片描述

查看消费者消费消息的消费位置的命令

1.查看当前有哪些消费者组
 ./kafka -consumer-groups.sh  --bootstrap-server hadoop110:9092,hadoop112:9092,hadoop113:9092 --group --list
 假如有:console- consumer - 32560正在消费
 2.查看消费者组消费到那个位置
 ./kafka- consumer-groups.sh --bootstrap-server hadoop110:9092,hadoop112:9092,hadoop113:9092 --group console- consumer - 32560 --describe

在这里插入图片描述

1、Recevice 模式

  • 1.6后就没有了这种模式,这种模式的offset是由zookeeper来维护的无法手动维护,
public class SparkStreamingOnKafkaReceiver {
 
	public static void main(String[] args) {
		SparkConf conf = new SparkConf().setAppName("SparkStreamingOnKafkaReceiver").setMaster("local[2]");
		conf.set("spark.streaming.receiver.writeAheadLog.enable","true");
		conf.set("spark.streaming.concurrentJobs", "10");
		
		JavaStreamingContext jsc = new JavaStreamingContext(conf, Durations.seconds(5));
  		jsc.checkpoint("d://receivedata");
		/**
		 * 可以直接读取Flume
		 */
//		JavaReceiverInputDStream lines = FlumeUtils.createStream(jsc,"192.168.5.1", 9999);
//		JavaReceiverInputDStream lines = FlumeUtils.createPollingStream(jsc, "192.168.5.128", 8899);
		
		
		Map<String, Integer> topicConsumerConcurrency = new HashMap<String, Integer>();
		topicConsumerConcurrency.put("kfk", 1);
		
		/**
		 * 第一个参数是StreamingContext
		 * 第二个参数是ZooKeeper集群信息(接受Kafka数据的时候会从Zookeeper中获得Offset等元数据信息)
		 * 第三个参数是Consumer Group
		 * 第四个参数是消费的Topic以及并发读取Topic中Partition的线程数
		 * 
		 * 注意:
		 * 1、KafkaUtils.createStream 使用五个参数的方法,设置receiver的存储级别
		 * 2、在java里面使用多个receiver,需要将JavaPairReceiverInputDStream转换成javaDstream使用toJavaDstream
		 * 
		 */
		JavaPairReceiverInputDStream<String,String> lines = KafkaUtils.createStream(
				jsc,
				"192.168.126.111:2181,192.168.126.112:2181,192.168.126.113:2181",
				"MyFirstConsumerGroup", 
				topicConsumerConcurrency/*,StorageLevel.MEMORY_AND_DISK()*/);
		
		
		JavaDStream<String> words = lines.flatMap(new FlatMapFunction<Tuple2<String,String>, String>() { 
			//如果是Scala,由于SAM转换,所以可以写成val words = lines.flatMap { line => line.split(" ")}

			/**
			 * 
			 */
			private static final long serialVersionUID = 1L;

			public Iterable<String> call(Tuple2<String,String> tuple) throws Exception {
				return Arrays.asList(tuple._2.split("\t"));
			}
		});
		
		  
		JavaPairDStream<String, Integer> pairs = words.mapToPair(new PairFunction<String, String, Integer>() {

			/**
			 * 
			 */
			private static final long serialVersionUID = 1L;

			public Tuple2<String, Integer> call(String word) throws Exception {
				return new Tuple2<String, Integer>(word, 1);
			}
		});
		
		  
		JavaPairDStream<String, Integer> wordsCount = pairs.reduceByKey(new Function2<Integer, Integer, Integer>() { 
			//对相同的Key,进行Value的累计(包括Local和Reducer级别同时Reduce)
			
			/**
			 * 
			 */
			private static final long serialVersionUID = 1L;

			public Integer call(Integer v1, Integer v2) throws Exception {
				return v1 + v2;
			}
		});
		
		 
		wordsCount.print();
		
		jsc.start();
		jsc.awaitTermination();
		jsc.close();
	}

}
  • 下篇文章Redis如何维护offset
发布了27 篇原创文章 · 获赞 10 · 访问量 2472

猜你喜欢

转载自blog.csdn.net/qq_42890382/article/details/104536604