Apache kafka Spring Kafka事务-发布到主题的重复消息

Apache kafka Spring Kafka事务-发布到主题的重复消息,apache-kafka,spring-transactions,spring-kafka,Apache Kafka,Spring Transactions,Spring Kafka,我们正在尝试为我们的生产商实现事务。我们的用例是从MQ接收消息并发布到kafka。当出现故障时,我们需要回滚发布到kafka的消息,而不向MQ发送确认 当我们使用事务时,我们会在kafka主题中看到重复的消息 @Bean("producerConfig") public Properties producerConfig() { LOGGER.info("Creating Dev Producer Configs"); Properties configs = new Prope

我们正在尝试为我们的生产商实现事务。我们的用例是从MQ接收消息并发布到kafka。当出现故障时,我们需要回滚发布到kafka的消息,而不向MQ发送确认

当我们使用事务时,我们会在kafka主题中看到重复的消息

@Bean("producerConfig")
public Properties producerConfig() {
    LOGGER.info("Creating Dev Producer Configs");
    Properties configs = new Properties();
    configs.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, localhost:9092);
    configs.put(ProducerConfig.ACKS_CONFIG, "all");
    configs.put(ProducerConfig.RETRIES_CONFIG, 1);
    configs.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, 1);
    configs.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
    configs.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
    configs.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
    return configs;
}

@Bean
public ProducerFactory<String, String> producerFactory() {
    DefaultKafkaProducerFactory<String, String> producerFactory = new DefaultKafkaProducerFactory<>(new HashMap<String, Object>((Map) producerConfig));
    producerFactory.setTransactionIdPrefix("spring-kafka-transaction");
    return producerFactory;
}

@Bean
public KafkaTemplate<String, String> kafkaTemplate() {
    KafkaTemplate<String, String> kafkaTemplate = new KafkaTemplate<>(producerFactory());
    kafkaTemplate.setDefaultTopic(topic);
    return kafkaTemplate;
}


@Bean
KafkaTransactionManager<String,String> kafkaTransactionManager(){
    KafkaTransactionManager<String, String> transactionManager = new KafkaTransactionManager<>(producerFactory());
    return transactionManager;
}
@Bean(“producerConfig”)
公共属性producerConfig(){
LOGGER.info(“创建开发生产者配置”);
Properties configs=新属性();
configs.put(ProducerConfig.BOOTSTRAP\u SERVERS\u CONFIG,localhost:9092);
configs.put(ProducerConfig.ACKS_CONFIG,“all”);
configs.put(ProducerConfig.RETRIES\u CONFIG,1);
configs.put(ProducerConfig.MAX\u-IN\u-FLIGHT\u-REQUESTS\u-PER\u-CONNECTION,1);
configs.put(ProducerConfig.KEY\u SERIALIZER\u CLASS\u CONFIG,StringSerializer.CLASS);
configs.put(ProducerConfig.VALUE\u SERIALIZER\u CLASS\u CONFIG,StringSerializer.CLASS);
configs.put(ProducerConfig.ENABLE\u idemptence\u CONFIG,true);
返回配置;
}
@豆子
公共生产工厂生产工厂(){
DefaultKafkaProducerFactory producerFactory=newdefaultkafkaproducerfactory(newhashmap((Map)producerConfig));
setTransactionIdPrefix(“春季卡夫卡交易”);
返回生产厂;
}
@豆子
公共卡夫卡模板卡夫卡模板(){
KafkaTemplate KafkaTemplate=新的KafkaTemplate(producerFactory());
kafkaTemplate.setDefaultTopic(主题);
返回卡夫卡模板;
}
@豆子
KafkaTransactionManager KafkaTransactionManager(){
KafkaTransactionManager transactionManager=新的KafkaTransactionManager(producerFactory());
返回事务管理器;
}
侦听器方法

@Component
public class WMQListener implements MessageListener {

KafkaTemplate<String, String> kafkaTemplate;

@Override
@Transactional
public void onMessage(Message message) {
    String onHandXmlStr = null;
    try {
        if (message instanceof TextMessage) {
            TextMessage textMessage = (TextMessage) message;
            onHandXmlStr = textMessage.getText();
        }
        LOGGER.debug("Message Received from WMQ :: " + onHandXmlStr);
        Msg msg = JaxbUtil.convertStringToMsg(onHandXmlStr);
        List<String> onHandList = DCMUtil.convertMsgToList(msg);

        ListenableFuture send = kafkaTemplate.sendDefault(onHandList.get(0));
        send.addCallback(new ListenableFutureCallback() {
            @Override
            public void onFailure(Throwable ex) {
                ex.printStackTrace();
            }

            @Override
            public void onSuccess(Object result) {
                System.out.println(result);
            }
        });
     message.acknowledge();

}
@组件
公共类WMQListener实现MessageListener{
卡夫卡坦普尔卡夫卡坦普尔;
@凌驾
@交易的
消息(消息消息)上的公共无效{
字符串onHandXmlStr=null;
试一试{
如果(文本消息的消息实例){
text消息text消息=(text消息)消息;
onHandXmlStr=textMessage.getText();
}
debug(“从WMQ收到的消息::”+onHandXmlStr);
Msg Msg=JaxbUtil.convertStringToMsg(onHandXmlStr);
List onHandList=dcmulti.convertMsgToList(msg);
ListenableFuture send=kafkaTemplate.sendDefault(onHandList.get(0));
send.addCallback(新ListenableFutureCallback(){
@凌驾
失效时的公共无效(可丢弃的ex){
例如printStackTrace();
}
@凌驾
成功时公共无效(对象结果){
系统输出打印项次(结果);
}
});
message.acknowledge();
}
然而,我想知道为什么偏移量增加了两倍

由于kafka主题是一个线性日志(每个分区),回滚的消息仍然占据日志中的一个位置(猜测)

考虑一下

  • p1.发送(发送)(偏移量23)
  • p2.发送(发送)(偏移量24)
  • p1.回滚
  • p2.提交
  • p1.重新发送(发送)(偏移量25)
  • p1.承诺
我的猜测是,偏移量23处的p1记录只是标记为回滚,而不是发送给消费者(除非在使用read_uncommitted隔离写入时处于活动状态)

编辑

我认为有/没有交易的抵销没有区别

@SpringBootApplication
@EnableTransactionManagement
public class So48196671Application {

    public static void main(String[] args) throws Exception {
        ConfigurableApplicationContext ctx = SpringApplication.run(So48196671Application.class, args);
        Thread.sleep(15_000);
        ctx.close();
        System.exit(0);
    }

    @Bean
    public ApplicationRunner runner(Foo foo) {
        return args -> foo.send("bar");
    }

    @Bean
    public KafkaTransactionManager<String, String> transactionManager(ProducerFactory<String, String> pf) {
        return new KafkaTransactionManager<>(pf);
    }

    @KafkaListener(id = "baz", topics = "so48196671")
    public void listen(String in, @Header(KafkaHeaders.OFFSET) long offset) {
        System.out.println(in + " @ " + offset) ;
    }

    @Component
    public static class Foo {

        @Autowired
        KafkaTemplate<String, String> template;

        @Transactional
        public void send(String out) throws Exception {
            ListenableFuture<SendResult<String, String>> sent = template.send("so48196671", out);
            SendResult<String, String> sendResult = sent.get();
            System.out.println(out + " sent to " + sendResult.getRecordMetadata().offset());
            Thread.sleep(5_000);
        }

    }

}
但是,是的,在故障情况下,会使用额外的插槽

@SpringBootApplication
@EnableTransactionManagement
public class So48196671Application {

    public static void main(String[] args) throws Exception {
        ConfigurableApplicationContext ctx = SpringApplication.run(So48196671Application.class, args);
        Thread.sleep(15_000);
        ctx.close();
        System.exit(0);
    }

    @Bean
    public ApplicationRunner runner(Foo foo) {
        return args -> {
            try {
                foo.send("bar");
            }
            catch (Exception e) {
                //
            }
            foo.send("bar");
        };
    }

    @Bean
    public KafkaTransactionManager<String, String> transactionManager(ProducerFactory<String, String> pf) {
        return new KafkaTransactionManager<>(pf);
    }

    @KafkaListener(id = "baz", topics = "so48196671")
    public void listen(String in, @Header(KafkaHeaders.OFFSET) long offset) {
        System.out.println(in + " @ " + offset) ;
    }

    @Component
    public static class Foo {

        private boolean fail = true;

        @Autowired
        KafkaTemplate<String, String> template;

        @Transactional
        public void send(String out) throws Exception {
            ListenableFuture<SendResult<String, String>> sent = template.send("so48196671", out);
            SendResult<String, String> sendResult = sent.get();
            System.out.println(out + " sent to " + sendResult.getRecordMetadata().offset());
            if (fail) {
                fail = false;
                throw new RuntimeException();
            }
        }

    }

}
在下一次跑步中

bar sent to 29
bar sent to 31
bar @ 31
如果我删除了异常,在下一次运行时,我会

bar sent to 33
bar sent to 35
bar @ 33
bar @ 35
因此,是的,事务本身似乎占据了日志中的一个位置


因此,这并不是一条真正重复的消息-您可能可以在他们的设计文档中阅读它。

您的
@配置类中是否有
@EnableTransactionManagement
?您是否将您的消费者
隔离级别配置
更改为
已提交
?如果两者都有,是否有调试日志(对于
org.apache.kafka
org.springframework.transaction
)帮助您了解发生了什么?是的,我有
@EnableTransactionManagement
,但我目前正在测试生产者应用程序,还没有启动使用者,因为我观察到一条消息的偏移量增加了2。我有一个问题。今天我通过在同一主题上启动控制台使用者进行测试,但没有指定
 明确阅读提交的
,但我只收到了消息,而没有收到副本。然而,我想知道为什么当我使用
@Transaction
KafkaTransactionManager
时,偏移量增加了两倍,并且在没有它的情况下不会发生。是的,如果出现任何故障并且回滚了操作,这是正确的,但在正常场景中ios,它不应该回滚一个事务,并且偏移量不应该增加两个。请用一个示例和日志准确解释您的意思。如果我不能提供足够的详细信息或者解释得不好,请原谅,我正在发送一条没有使用
@Transactional
KafkaTransactionManager
的消息.send()
我的偏移量增加了一个。现在,我已经将
@Transactional
添加到我的侦听器中,并再次发送一条消息。在这种情况下,偏移量增加了两个,但如果我发送的消息如您所解释的p1.send(tx)(偏移量23),我没有任何事务回滚或任何类型的故障在这种情况下,我应该将偏移量设置为23,但当我看到偏移量时,它会增加到24。因为没有任何故障,所以不会发生回滚。此外,我观察到我的使用者只收到一条消息。但是,日志偏移量增加了两条,并且我始终会看到一条消息的延迟,即使我的使用者已启动并正在运行。我正在使用read_com在我的消费者。
bar sent to 29
bar sent to 31
bar @ 31
bar sent to 33
bar sent to 35
bar @ 33
bar @ 35