程序结束后Spring Kafka全局事务ID保持打开

时间:2017-12-14 15:35:41

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

我正在Spring Boot下创建一个Kafka Spring生产者,它将数据发送到Kafka,然后写入数据库;我希望所有工作都在一次交易中。我是卡夫卡的新手,也不是春天的专家,我遇到了一些困难。任何指针都非常赞赏。

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

producerFactory.setTransactionIdPrefix("MY_SERVER");

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

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

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

在我看来,原始的transID仍在服务器上打开,并且从未提交过。 (我可以使用console-consumer读取该主题的数据,但这将是未提交的读取)。但如果是这样的话,我如何让春天提交反式?我在想我的构造一定是错的。或者 - 问题可能是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


}

1 个答案:

答案 0 :(得分:0)

无论我运行多少次,这都适用于我(但我必须在本地计算机上运行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);
        }

    }

}