【问题标题】:Spring Kafka global transaction ID stays open after program endsSpring Kafka 全局事务 ID 在程序结束后保持打开状态
【发布时间】:2017-12-14 15:35:41
【问题描述】:

我正在 Spring Boot 下创建一个 Kafka Spring 生产者,它将数据发送到 Kafka,然后写入数据库;我希望所有这些工作都在一个事务中。我是 Kafka 的新手,也不是 Spring 方面的专家,并且遇到了一些困难。任何指针都非常感谢。

到目前为止,我的代码已成功循环写入 Kafka。我还没有设置 数据库,但已通过在配置中向 producerFactory 添加 transactionIdPrefix 来设置全局事务:

producerFactory.setTransactionIdPrefix("MY_SERVER");

并将@Transactional 添加到 Kafka 发送的方法中。最终我打算用同样的方法来做我的数据库工作。

问题:代码第一次运行良好。但是,如果我停止程序,即使是干净地,我发现代码在我第二次运行它时会挂起,因为它进入 @Transactional 方法。如果我注释掉@Transactional,它会进入方法但挂在kafa模板send()上。

问题似乎出在交易 ID 上。如果我更改前缀并重新运行,程序第一次运行良好,但再次运行时挂起,直到选择新前缀。由于重启后 trans ID 计数器从零开始,如果 trans ID 前缀没有改变,那么重启时将使用相同的 trans ID。

在我看来,原来的 transID 仍然在服务器上打开,并且从未提交过。 (我可以使用控制台消费者从主题中读取数据,但这将读取未提交)。但如果是这样的话,我如何让 spring 提交 trans?我想我的配置一定是错误的。或者——这个问题是否可能是反式 ID 永远无法重复使用? (在这种情况下,如何解决?)

这是我的相关代码。配置是:

@SpringBootApplication
public class MYApplication {

@Autowired
private static ChangeSweeper changeSweeper;


@Value("${kafka.bootstrap-servers}")
private String bootstrapServers;

@Bean
public ProducerFactory<String, String> producerFactory() {
        Map<String, Object> configProps = new HashMap<>();
        configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
        configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);

        DefaultKafkaProducerFactory<String, String> producerFactory=new DefaultKafkaProducerFactory<>(configProps); 
        producerFactory.setTransactionIdPrefix("MY_SERVER"); 
        return  producerFactory;
}

@Bean
public KafkaTransactionManager<String, String> KafkaTransactionManager() {
    return new KafkaTransactionManager<String, String>((producerFactory()));
}

@Bean(name="kafkaProducerTemplate")
public KafkaTemplate<String, String> kafkaProducerTemplate() {
    return new KafkaTemplate<>(producerFactory());
}

而进行交易的方法是:

@Transactional
public void send( final List<Record> records) {
    logger.debug("sending {} records; batchSize={}; topic={}", records.size(),batchSize,  kafkaTopic);

    // Divide the record set into batches of size batchSize and send each batch with a kafka transaction:
    for (int batchStartIndex = 0; batchStartIndex < records.size(); batchStartIndex += batchSize ) {
        int batchEndIndex=Math.min(records.size()-1, batchStartIndex+batchSize-1);
        List<Record> nextBatch = records.subList(batchStartIndex, batchEndIndex);
        logger.debug("## batch is from " + batchStartIndex + " to " + batchEndIndex);           

        for (Record record : nextBatch) {

            kafkaProducerTemplate.send( kafkaTopic, record.getKey().toString(), record.getData().toString());   
            logger.debug("Sending> " + record);
        }

// I will put the DB writes here


}

【问题讨论】:

  • > is the issue possibly that trans ID's can never be reused - 应该不是问题,因为我们调用 initTransactions() - 它的 javadocs 说 If the previous instance had failed with a transaction in progress, it will be aborted. - 我会看看我是否可以重现您的问题。
  • 您的集群中是否有三个代理(默认要求)?我在initTransactions() 上挂断了,只有一个代理,在服务器日志上,我看到Number of alive brokers '1' does not meet the required replication factor '3' for the transactions state topic (configured via 'transaction.state.log.replication.factor') - 但是你的第一次发送不起作用。
  • 我只有一个经纪人——只是在本地开发。正如您所指出的,发送工作正常,一个接一个,一批接一批,直到我终止程序并重新启动。所以发送似乎没问题,但交易似乎是问题所在。请注意,我现在在 Wndows 上运行,我注意到了其他问题。
  • 看我的回答;另请参阅this github issue,其中记者引用了apache/kafka#3283关于Windoze的一些问题。

标签: spring apache-kafka spring-transactions spring-kafka


【解决方案1】:

无论我运行多少次,这对我来说都很好(但我必须在本地机器上运行 3 个代理实例,因为默认情况下事务需要这样做)...

@SpringBootApplication
@EnableTransactionManagement
public class So47817034Application {

    public static void main(String[] args) {
        SpringApplication.run(So47817034Application.class, args).close();
    }

    private final CountDownLatch latch = new CountDownLatch(2);

    @Bean
    public ApplicationRunner runner(Foo foo) {
        return args -> {
            foo.send("foo");
            foo.send("bar");
            this.latch.await(10, TimeUnit.SECONDS);
        };
    }

    @Bean
    public KafkaTransactionManager<Object, Object> KafkaTransactionManager(KafkaProperties properties) {
        return new KafkaTransactionManager<Object, Object>(kafkaProducerFactory(properties));
    }

    @Bean
    public ProducerFactory<Object, Object> kafkaProducerFactory(KafkaProperties properties) {
        DefaultKafkaProducerFactory<Object, Object> factory =
                new DefaultKafkaProducerFactory<Object, Object>(properties.buildProducerProperties());
        factory.setTransactionIdPrefix("foo-");
        return factory;
    }

    @KafkaListener(id = "foo", topics = "so47817034")
    public void listen(String in) {
        System.out.println(in);
        this.latch.countDown();
    }

    @Component
    public static class Foo {

        @Autowired
        private KafkaTemplate<Object, Object> template;

        @Transactional
        public void send(String go) {
            this.template.send("so47817034", go);
        }

    }

}

【讨论】:

  • 非常感谢您的关注和回复。我将尝试使用 1 个代理和 3 个代理以您的代码为模型,然后让您知道会发生什么。
  • 我将代理增加到 3 个,但我自己的代码继续挂起。然后我运行了您的代码(奇怪的是,KafkaListener 在启动时导致了 lambda api 异常'我将其注释掉并使用控制台-kafka-consumer 来监视主题)。我的代码遇到了完全相同的问题:出现了“foo”但没有出现“bar”,程序挂起。第二次运行没有发送任何东西,它挂在“foo”上。如果更改了 trans ID,它会像以前一样发送 foo,但会挂在 bar 上。删除交易修复了它。我认为 kafka 事务在 Windows 10 上出现问题。可以在 Unix 上尝试。
  • FWIW,我在 Mac OS 上运行了这个。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-11
  • 2021-07-17
  • 2013-11-14
  • 2013-10-21
  • 2012-07-03
  • 2023-04-11
相关资源
最近更新 更多