适用于Apache Kafka的Spring:如何分区的seekToEnd?

时间:2018-08-17 21:37:25

标签: spring spring-boot apache-kafka spring-kafka

我正在将Spring Boot 2.0.2.RELEASE与Spring一起用于Apache Kafka(有效的pom显示了2.1.6.RELEASE版本的spring-kafka)。

我从使用常规的ByteArrayDeserializer到使用Confluent的反序列化器

properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, io.confluent.kafka.serializers.KafkaAvroDeserializer.class);

因此,我不必获取字节,然后将其反序列化为有效负载等。但是,其副作用是一些旧消息-我无法再读取了,因为它们的架构在融合的注册表。

因此,当我启动应用程序时,我会不断收到此消息

    2018-08-17 17:58:51.360 ERROR 18004 --- [ntainer#0-0-C-1] o.s.k.listener.BatchLoggingErrorHandler  : Error while processing:

org.apache.kafka.common.errors.SerializationException: Error deserializing key/value for partition ri00-q-log-et-final-0 at offset 36833. If needed, please seek past the record to continue consumption.
Caused by: org.apache.kafka.common.errors.SerializationException: Error deserializing Avro message for id -1
Caused by: org.apache.kafka.common.errors.SerializationException: Unknown magic byte!

所以我决定我必须从主题的结尾开始聆听,我已经查看了文档https://docs.spring.io/spring-kafka/reference/htmlsingle/#seek 建议实施ConsumerSeekAware及其子接口ConsumerSeekAware.ConsumerSeekCallback

我更改了包含@KafkaListener方法的@service类 实现文档

中提到的接口
@Service
 public class MyAvroListener implements 
 ConsumerSeekAware.ConsumerSeekCallback,ConsumerSeekAware {

,它具有@kafkalistener注释的方法,在此方法中,我尝试过寻找分区的ToEnd

@KafkaListener(topics = "${topic}", containerFactory = "myAvroListenerFactory")
    public void listen(final Acknowledgment ack, final List<ConsumerRecord<String, EclLogging>> messages) throws Exception {
    this.seekCallBack.get().seekToEnd(topic,0);
    try {
        for (ConsumerRecord<String, EclLogging> kafkaRecord : messages) {

我也尝试过寻求特定的偏移量(因为我一直陷在36833偏移量消息中)

@KafkaListener(topics = "${topic}", containerFactory = "myAvroListenerFactory")
        public void listen(final Acknowledgment ack, final List<ConsumerRecord<String, EclLogging>> messages) throws Exception {
        this.seekCallBack.get().seek(topic,0,36900);
        try {
            for (ConsumerRecord<String, EclLogging> kafkaRecord : messages) {

我已经通过上述接口实现了方法

private final ThreadLocal<ConsumerSeekCallback> seekCallBack = new ThreadLocal<>();

@Override
public void registerSeekCallback(ConsumerSeekCallback consumerSeekCallback) {
    this.seekCallBack.set(consumerSeekCallback);
}

@Override
public void onPartitionsAssigned(Map<TopicPartition, Long> map, ConsumerSeekCallback consumerSeekCallback) {

}

@Override
public void onIdleContainer(Map<TopicPartition, Long> map, ConsumerSeekCallback consumerSeekCallback) {

}

@Override
public void seek(String s, int i, long l) {

}

@Override
public void seekToBeginning(String s, int i) {

}

@Override
public void seekToEnd(String topic, int partition) {
    System.out.println("seekToEnd is hit for topic s = " + topic + " and partition i=" + partition);
}

应用启动时,registerSeekCallBack方法确实被点击, 但seekToEnd或seek方法没有被点击。

因此我不断收到此消息

    2018-08-17 17:58:51.360 ERROR 18004 --- [ntainer#0-0-C-1] o.s.k.listener.BatchLoggingErrorHandler  : Error while processing:

org.apache.kafka.common.errors.SerializationException: Error deserializing key/value for partition ri00-q-log-et-final-0 at offset 36833. If needed, please seek past the record to continue consumption.
Caused by: org.apache.kafka.common.errors.SerializationException: Error deserializing Avro message for id -1
Caused by: org.apache.kafka.common.errors.SerializationException: Unknown magic byte!

我正在从这里使用代码段 Spring Kafka Template implementaion example for seek offset, acknowledgement

如此处What determines Kafka consumer offset?所述,我无法使用auto.offset.reset属性从主题末尾开始使用(除非我使用其他consumerGroupId,在我的情况下这是不可能的)。我想知道是否可以使用现有的消费者群体解决这个问题。

3 个答案:

答案 0 :(得分:1)

当您已经发送了具有不同架构的消息时,您将拥有一个主题。

这个问题可以通过多种方式解决。

### Deletes all schema versions registered under the subject "Kafka-value"
$ curl -X DELETE http://localhost:8081/subjects/Kafka-value
  [1]

### Deletes version 1 of the schema registered under subject "Kafka-value"
$ curl -X DELETE http://localhost:8081/subjects/Kafka-value/versions/1
  1

### Deletes the most recently registered schema under subject "Kafka-value"
$ curl -X DELETE http://localhost:8081/subjects/Kafka-value/versions/latest

以上API主要旨在用于开发环境,在开发环境中,在最终确定模式之前通常需要进行迭代。尽管不建议在生产环境中使用这些API,但在极少数情况下可以在生产中使用这些API,但要格外小心。

  • 要注册的新架构与现有架构版本之一存在兼容性问题

  • 该模式的旧版本需要针对同一主题重新注册

  • 仅在实时流系统中使用该架构,而绝对不再需要旧版本

  • 主题需要回收

还必须注意,在使用“删除主题”或删除唯一可用的架构版本时,也会删除该主题的所有已注册兼容性设置。

第二种方法是开始向新主题发送消息。请按照下列步骤操作,就可以了。

  • 更新生产者以将数据发送到新主题,新主题将在模式注册表中注册更新的模式
  • 验证对于该主题的所有消费者,延迟为零
  • 更新使用者以使用新主题中的数据
  • 从kafka删除旧主题

答案 1 :(得分:0)

您执行搜索的时间太晚了-在获取记录的poll()之后;您需要在以下位置进行搜索

@Override
public void onPartitionsAssigned(Map<TopicPartition, Long> map, ConsumerSeekCallback consumerSeekCallback) {

}
通过在此致电consumerSeekCallback.seekToEnd(...)

。搜索将在poll()之前进行以获取记录。

您还可以使用kafka-consumer-groups命令行工具为组/主题/分区设置任意偏移。

当前的引导版本是2.0.4,kafka 2.1.8。

此外,您不应该实现传递给您的回调。

文档似乎很清楚...

  

使用组管理时,分配更改时将调用第二种方法。您可以使用此方法,例如,通过调用回调来设置分区的初始偏移量。您必须使用回调参数,而不是传递给registerSeekCallback的参数。

...如果没有,我们应该改变什么?

答案 2 :(得分:0)

基于@Gary Russell的反馈,我对代码进行了以下更改以使其正常工作。谢谢@Gary和@ Manjeet / @ cricket_007

所以我做了以下工作,

基本上没有更改标注为@KafkaListener的方法,但包含该方法的类必须实现这些接口

MyKafkaListenerClass implements ConsumerSeekAware.ConsumerSeekCallback,ConsumerSeekAware

然后在该类中,我从这些接口实现方法...

    private final ThreadLocal<ConsumerSeekCallback> seekCallBack = new ThreadLocal<>();

@Override
public void registerSeekCallback(ConsumerSeekCallback consumerSeekCallback) {
    this.seekCallBack.set(consumerSeekCallback);
}

@Override
public void onPartitionsAssigned(Map<TopicPartition, Long> map, ConsumerSeekCallback consumerSeekCallback) {
    this.seekCallBack.get().seekToEnd(topic,0);
}

@Override
public void onIdleContainer(Map<TopicPartition, Long> map, ConsumerSeekCallback consumerSeekCallback) {

}

@Override
public void seek(String topic, int partition, long offset) {
    System.out.println("seekToEnd is hit for topic= " + topic + " and partition=" + partition+ " and offset =" + offset);
}

@Override
public void seekToBeginning(String s, int i) {

}

@Override
public void seekToEnd(String topic, int partition) {
    System.out.println("seekToEnd is hit for topic s = " + topic + " and partition i=" + partition);
}