我正在将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,在我的情况下这是不可能的)。我想知道是否可以使用现有的消费者群体解决这个问题。
答案 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,但要格外小心。
要注册的新架构与现有架构版本之一存在兼容性问题
该模式的旧版本需要针对同一主题重新注册
仅在实时流系统中使用该架构,而绝对不再需要旧版本
主题需要回收
还必须注意,在使用“删除主题”或删除唯一可用的架构版本时,也会删除该主题的所有已注册兼容性设置。
第二种方法是开始向新主题发送消息。请按照下列步骤操作,就可以了。
答案 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);
}