重置Kafka消费者组位移

1.重置Kafka消费者组位移的好处

代码地址
重设消费者组的位移,是Kafka区别与其他的消息队列的一个重要的区别,也是Kafka的一个特色。也叫做消息重演(replayable)。传统的消息队列比如RabbitMq和ActiveMq 消息一旦被消费,就会被删除,Kafka因为它的两个特性 1.日志存储并且是持久化的方式 2.coordinator管理消费者组的位移。让我们可以对消费者组的位移操作的管理更加灵活。

在说怎样重设位移之前,需要先介绍consumer的一个参数概念

auto.offset.reset:指定了无位移消息或位移越界(consumer消费的位移不在当前消息日志的合理范围内)时kafka的应对策略。

  • earliest:最早的位移开始消费,不一定是0
  • lastest:最新处位移开始消费
  • none:如果未发现位移信息或者位移越界,抛出异常。基本不使用

重置消费者组位移是对消费者组的已经存在的位移做一些其他的策略,用来满足当前的业务需求。

2.概念以及代码实现

重置消费者组的位移可以按照两个维度来进行分配:

  • 位移维度
  • 时间维度

如下注解为所有策略的名称以及解释:

 public enum OffsetStrategyType implements StrategyType {
    //位移维度
    EARLIEST("Earliest","把位移调整到当前最早的位移处"),
    LATEST("Latest","最新位移处"),
    CURRENT("Current","当前最新提交位移处"),
    SPECIFIED_OFFSET("specified_offset","指定位移"),
    SHIFT_BY_N("shift_by_n","移动到当前位移差n处"),

    //时间维度
    DATE_TIME("date_time","调整到大于当前时间的最小位移处"),
    DURATION("duration","移动到当前时间指定间隔的位移处");
}


使用到的api主要就是这几个:

void seek(TopicPartition partition, long offset);
void seek(TopicPartition partition, OffsetAndMetadata offsetAndMetadata);
void seekToBeginning(Collection<TopicPartition> partitions);
void seekToEnd(Collection<TopicPartition> partitions);

2.1.消费者配置

需要注意一下三个点:

  • 1.如果kafka 开启了认证、授权的操作,需要配置赋予了相应权限的用户。

  • 2.需要制定对应的Consumer Group 的id,重置的是Consumer Group 的位移,需要知道去重置的那个消费者组的位移吧

  • 3.要关闭kakfa的自动位移提交功能。

  Properties props = new Properties();
        if (enableAuthentication) {
            //Authentication  使用 SASL/SCRAM 认证方式 开启认证配置
            InputStream in = ConsumerConfig.class.getClassLoader().getResourceAsStream("producer.properties");
            try {
                props.load(in);
            } catch (IOException e) {
                //todo handler produce.properties not exist
                e.printStackTrace();
            }
        }

        //必须填写
        props.put(org.apache.kafka.clients.consumer.ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, brokers);
        props.put(org.apache.kafka.clients.consumer.ConsumerConfig.GROUP_ID_CONFIG, groupId);
        props.put(org.apache.kafka.clients.consumer.ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, keyDeserializer);
        props.put(org.apache.kafka.clients.consumer.ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, valueDeserializer);

        //是否开启自动位移提交
        props.put(org.apache.kafka.clients.consumer.ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, enableAutoCommit);
        //从最早的消息开始读取
        props.put(org.apache.kafka.clients.consumer.ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
        props.put(org.apache.kafka.clients.consumer.ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, interceptors);
        return props;

2.2.如下代码为各种位移提交的策略,详情请见具体的注释:

public void resetOffset(OffsetStrategy offsetStrategy) {

        try (final KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(consumerProperties)) {
            consumer.subscribe(Collections.singleton(topic));
            //必须使用这个方法 不能使用 poll(Duration.ofSecond(0))
            consumer.poll(0);
            switch ((OffsetStrategyType) offsetStrategy.getStrategyType()) {
                case EARLIEST:
                    consumer.seekToBeginning(
                            consumer.partitionsFor(topic).stream().map(partitionInfo ->
                                    new TopicPartition(topic, partitionInfo.partition())).
                                    collect(Collectors.toList())
                    );
                    break;
                case LATEST:
                    consumer.seekToEnd(
                            consumer.partitionsFor(topic).stream().map(partitionInfo ->
                                    new TopicPartition(topic, partitionInfo.partition())
                            ).collect(Collectors.toList())
                    );
                    break;
                case CURRENT:
                    consumer.partitionsFor(topic).stream().map(partitionInfo ->
                            new TopicPartition(topic, partitionInfo.partition()))
                            .forEach(topicPartition -> {
                                long committedOffset = consumer.committed(topicPartition).offset();
                                consumer.seek(topicPartition, committedOffset);
                            });
                    break;
                case SPECIFIED_OFFSET:
                    consumer.partitionsFor(topic).stream().map(partitionInfo ->
                            new TopicPartition(topic, partitionInfo.partition()))
                            .forEach(topicPartition ->
                                    consumer.seek(topicPartition, offsetStrategy.getOffset()));
                    break;
                case SHIFT_BY_N:
                    consumer.partitionsFor(topic).stream().map(partitionInfo ->
                            new TopicPartition(topic, partitionInfo.partition()))
                            .forEach(topicPartition -> {
                                long committedOffset = consumer.committed(topicPartition).offset();
                                consumer.seek(topicPartition, committedOffset + offsetStrategy.getOffset());
                            });
                    break;

                case DATE_TIME:
                    //获取到当前时间的offset 类型为Map  并设置为指定时间的位移
                    consumer.offsetsForTimes(
                            consumer.partitionsFor(topic).stream().map(partitionInfo ->
                                    new TopicPartition(topic, partitionInfo.partition()))
                                    .collect(Collectors.toMap(Function.identity(), topicPartition -> offsetStrategy.getTime()))
                    ).entrySet().forEach(e -> {
                        consumer.seek(e.getKey(), e.getValue().offset());
                    });

                    break;
                case DURATION:
                    consumer.offsetsForTimes(
                            consumer.partitionsFor(topic).stream().map(partitionInfo ->
                                    new TopicPartition(topic, partitionInfo.partition()))
                                    .collect(Collectors.toMap(Function.identity(),
                                            topicPartition -> (System.currentTimeMillis() + offsetStrategy.getTime())))
                    ).entrySet().forEach(e -> {
                        consumer.seek(e.getKey(), e.getValue().offset());
                    });
                    break;
                default:
                    break;
            }
        }
    }

}

kafka提供了这些重置消费者组位移的操作,是我们对消息的消费更加灵活,容错性更高。而且由于kafka本身做的一下优化,这些操作的效率还都是很高的。

你可能感兴趣的:(kafka)