我正在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
}
答案 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);
}
}
}