Spring-kafka 2.1.xを使用してSpring Boot 2.0アプリケーションでDead Letter Queue(DLQ)の概念を実装する最良の方法は何ですか@ KafkaListenerいくつかの事前定義済みのKafka DLQトピックに送信され、単一のメッセージを失わないようにするいくつかのBeanのメソッド?
したがって、消費Kafkaレコードは次のいずれかです。
ErrorHandlerのカスタム実装でリスナーコンテナーを作成しようとしましたが、レコードの送信がKafkaTemplateを使用してDLQトピックに処理されませんでした。無効な自動コミットと[〜#〜] record [〜#〜] AckModeを使用します。
spring.kafka.enable-auto-ack=false
spring.kafka.listener.ack-mode=RECORD
@Configuration
public class KafkaConfig {
@Bean
ConcurrentKafkaListenerContainerFactory<Integer, String> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<Integer, String> factory = ...
...
factory.getContainerProperties().setErrorHandler(dlqErrorHandler);
return factory;
}
}
@Component
public class DlqErrorHandler implements ErrorHandler {
@Autowired
private KafkaTemplate<Object, Object> kafkaTemplate;
@Value("${dlqTopic}")
private String dlqTopic;
@Override
public void handle(Exception thrownException, ConsumerRecord<?, ?> record) {
log.error("Error, sending to DLQ...");
kafkaTemplate.send(dlqTopic, record.key(), record.value());
}
}
この実装はアイテム#を保証しないようです。 DlqErrorHandlerで例外がスローされる場合、レコードはリスナーによって再度消費されません。
トランザクションリスナーコンテナーの使用は役立ちますか?
factory.getContainerProperties().setTransactionManager(kafkaTransactionManager);
Spring Kafkaを使用してDLQコンセプトを実装する便利な方法はありますか?
2018年3月28日更新
Gary Russellの回答のおかげで、次のようにDlqErrorHandlerを実装することで目的の動作を実現できました
@Configuration
public class KafkaConfig {
@Bean
ConcurrentKafkaListenerContainerFactory<Integer, String> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<Integer, String> factory = ...
...
factory.getContainerProperties().setAckOnError(false);
factory.getContainerProperties().setErrorHandler(dlqErrorHandler);
return factory;
}
}
@Component
public class DlqErrorHandler implements ContainerAwareErrorHandler {
...
@Override
public void handle(Exception thrownException, list<ConsumerRecord<?, ?> records, Consumer<?, ?> consumer, MessageListenerContainer container) {
Consumerrecord<?, ? record = records.get(0);
try {
kafkaTemplate.send("dlqTopic", record.key, record.value());
consumer.seek(new TopicPartition(record.topic(), record.partition()), record.offset() + 1);
// Other records may be from other partitions, so seek to current offset for other partitions too
// ...
} catch (Exception e) {
consumer.seek(new TopicPartition(record.topic(), record.partition()), record.offset());
// Other records may be from other partitions, so seek to current offset for other partitions too
// ...
throw new KafkaException("Seek to current after exception", thrownException);
}
}
}
この方法で、コンシューマーポーリングが3つのレコード(1、2、3)を返し、2番目のレコードは処理できない場合:
DLQへの送信が失敗した場合、コンシューマはrecord.offset()を探し、レコードはリスナーに再配信されます(そしてDLQへの送信はおそらく廃止されます)。
SeekToCurrentErrorHandler
を参照してください。
例外が発生すると、未処理のすべてのレコードが次のポーリングで再配信されるように、コンシューマーを探します。
同じ手法(サブクラスなど)を使用してDLQに書き込み、DLQ書き込みが失敗した場合は現在のオフセット(およびその他の未処理)をシークし、DLQ書き込みが成功した場合は残りのレコードのみをシークできます。