Apache kafka Spring Kafka事务已启用,但消费者仍收到回滚消息
我正在为我的生产者和消费者应用程序使用SpringKafka事务 该要求在生产者方面有多个步骤:将消息发送到kafka,然后保存到db。如果保存到db失败,则希望回滚发送到kafka的消息 因此在消费者方面,我将Apache kafka Spring Kafka事务已启用,但消费者仍收到回滚消息,apache-kafka,spring-kafka,Apache Kafka,Spring Kafka,我正在为我的生产者和消费者应用程序使用SpringKafka事务 该要求在生产者方面有多个步骤:将消息发送到kafka,然后保存到db。如果保存到db失败,则希望回滚发送到kafka的消息 因此在消费者方面,我将isolation.leve设置为read\u committed,如果消息是从kafka回滚的,消费者不应该阅读它 生产商申请代码为: @Configuration @EnableKafka public class KafkaConfiguration { @Bean pu
isolation.leve
设置为read\u committed
,如果消息是从kafka回滚的,消费者不应该阅读它
生产商申请代码为:
@Configuration
@EnableKafka
public class KafkaConfiguration {
@Bean
public ProducerFactory<String, Customer> producerFactory() {
DefaultKafkaProducerFactory<String, Customer> pf = new DefaultKafkaProducerFactory<>(producerConfigs());
pf.setTransactionIdPrefix("customer.txn.tx-");
return pf;
}
@Bean
public Map<String, Object> producerConfigs() {
Map<String, Object> props = new HashMap<>();
// create a minimum Producer configs
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "http://127.0.0.1:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class);
props.put("schema.registry.url", "http://127.0.0.1:8081");
// create safe Producer
props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, "true");
props.put(ProducerConfig.ACKS_CONFIG, "all");
props.put(ProducerConfig.RETRIES_CONFIG, Integer.toString(Integer.MAX_VALUE));
props.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, "5"); // kafka 2.0 >= 1.1 so we can keep this as 5. Use 1 otherwise.
// high throughput producer (at the expense of a bit of latency and CPU usage)
props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy");
props.put(ProducerConfig.LINGER_MS_CONFIG, "20");
props.put(ProducerConfig.BATCH_SIZE_CONFIG, Integer.toString(32 * 1024)); // 32 KB batch size
return props;
}
@Bean
public KafkaTemplate<String, Customer> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
@Bean
public KafkaTransactionManager kafkaTransactionManager(ProducerFactory<String, Customer> producerFactory) {
KafkaTransactionManager<String, Customer> ktm = new KafkaTransactionManager<>(producerFactory);
ktm.setTransactionSynchronization(AbstractPlatformTransactionManager.SYNCHRONIZATION_ON_ACTUAL_TRANSACTION);
return ktm;
}
@Bean
@Primary
public JpaTransactionManager jpaTransactionManager(EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
@Bean(name = "chainedTransactionManager")
public ChainedTransactionManager chainedTransactionManager(JpaTransactionManager jpaTransactionManager,
KafkaTransactionManager kafkaTransactionManager) {
return new ChainedTransactionManager(kafkaTransactionManager, jpaTransactionManager);
}
}
@Component
@Slf4j
public class KafkaProducerService {
private KafkaTemplate<String, Customer> kafkaTemplate;
private CustomerConverter customerConverter;
private CustomerRepository customerRepository;
public KafkaProducerService(KafkaTemplate<String, Customer> kafkaTemplate, CustomerConverter customerConverter, CustomerRepository customerRepository) {
this.kafkaTemplate = kafkaTemplate;
this.customerConverter = customerConverter;
this.customerRepository = customerRepository;
}
@Transactional(transactionManager = "chainedTransactionManager", rollbackFor = Exception.class)
public void sendEvents(String topic, CustomerModel customer) {
LOGGER.info("Sending to Kafka: topic: {}, key: {}, customer: {}", topic, customer.getKey(), customer);
// kafkaTemplate.send(topic, customer.getKey(), customerConverter.convertToAvro(customer));
kafkaTemplate.executeInTransaction(kt -> kt.send(topic, customer.getKey(), customerConverter.convertToAvro(customer)));
customerRepository.saveToDb();
}
}
@配置
@使能卡夫卡
公共类卡夫卡配置{
@豆子
公共生产工厂生产工厂(){
DefaultKafkaProducerFactory pf=新的DefaultKafkaProducerFactory(producerConfigs());
pf.setTransactionIdPrefix(“customer.txn.tx-”);
返回pf;
}
@豆子
公共地图产品配置(){
Map props=newhashmap();
//创建最低生产商配置
props.put(ProducerConfig.BOOTSTRAP\u SERVERS\u CONFIG,“http://127.0.0.1:9092");
put(ProducerConfig.KEY\u SERIALIZER\u CLASS\u CONFIG,StringSerializer.CLASS);
put(ProducerConfig.VALUE\u SERIALIZER\u CLASS\u CONFIG,KafkaAvroSerializer.CLASS);
put(“schema.registry.url”http://127.0.0.1:8081");
//创建安全生产商
props.put(ProducerConfig.ENABLE_idemptence_CONFIG,“true”);
props.put(ProducerConfig.ACKS_CONFIG,“all”);
put(ProducerConfig.RETRIES\u CONFIG,Integer.toString(Integer.MAX\u VALUE));
props.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION,“5”);//kafka 2.0>=1.1,因此我们可以将其保留为5。否则使用1。
//高吞吐量生产者(以牺牲一点延迟和CPU使用为代价)
props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG,“snappy”);
props.put(ProducerConfig.LINGER\u MS\u CONFIG,“20”);
props.put(ProducerConfig.BATCH\u SIZE\u CONFIG,Integer.toString(32*1024));//32 KB的批大小
返回道具;
}
@豆子
公共卡夫卡模板卡夫卡模板(){
返回新的卡夫卡模板(producerFactory());
}
@豆子
公共KafkaTransactionManager KafkaTransactionManager(生产工厂生产工厂){
KafkaTransactionManager ktm=新的KafkaTransactionManager(生产工厂);
ktm.setTransactionSynchronization(AbstractPlatformTransactionManager.SYNCHRONIZATION在实际事务上);
返回ktm;
}
@豆子
@初级的
公共JpaTransactionManager JpaTransactionManager(EntityManager工厂EntityManager工厂){
返回新的JpaTransactionManager(entityManagerFactory);
}
@Bean(name=“chainedTransactionManager”)
公共链TransactionManager链TransactionManager(JpaTransactionManager JpaTransactionManager,
卡夫卡塔拉行动经理(卡夫卡塔拉行动经理){
返回新的ChainedTransactionManager(KafkatTransactionManager、jpaTransactionManager);
}
}
@组成部分
@Slf4j
公共类KafkaProducerService{
私人卡夫卡模板卡夫卡模板;
私人顾客转化者顾客转化者;
私人客户存储客户存储;
公共KafkaProducerService(KafkaTemplate KafkaTemplate,CustomerConverter CustomerConverter,CustomerRepository CustomerRepository){
this.kafkaTemplate=kafkaTemplate;
this.customerConverter=customerConverter;
this.customerRepository=customerRepository;
}
@事务性(transactionManager=“chainedTransactionManager”,rollboor=Exception.class)
公共void sendEvents(字符串主题,CustomerModel客户){
info(“发送到Kafka:topic:{},key:{},customer:{}”,topic,customer.getKey(),customer);
//kafkaTemplate.send(主题,customer.getKey(),customerConverter.convertToAvro(客户));
kafkaTemplate.executeInTransaction(kt->kt.send(主题,customer.getKey(),customerConverter.convertToAvro(客户));
customerRepository.saveToDb();
}
}
所以我在saveToDb方法中显式抛出一个异常,我可以看到异常抛出。但是消费者应用程序仍然可以看到消息
消费者代码:
@Slf4j
@Configuration
@EnableKafka
public class KafkaConfiguration {
@Bean
ConcurrentKafkaListenerContainerFactory<String, Customer> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, Customer> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
factory.setAfterRollbackProcessor(new DefaultAfterRollbackProcessor<String, Customer>(-1));
// SeekToCurrentErrorHandler errorHandler =
// new SeekToCurrentErrorHandler((record, exception) -> {
// // recover after 3 failures - e.g. send to a dead-letter topic
//// LOGGER.info("***in error handler data, {}", record);
//// LOGGER.info("***in error handler headers, {}", record.headers());
//// LOGGER.info("value: {}", new String(record.headers().headers("springDeserializerExceptionValue").iterator().next().value()));
// }, 3);
//
// factory.setErrorHandler(errorHandler);
return factory;
}
@Bean
public ConsumerFactory<String, Customer> consumerFactory() {
return new DefaultKafkaConsumerFactory<>(consumerConfigs());
}
@Bean
public Map<String, Object> consumerConfigs() {
Map<String, Object> props = new HashMap<>();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
// props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
// props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, KafkaAvroDeserializer.class);
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ErrorHandlingDeserializer2.class);
props.put(ErrorHandlingDeserializer2.VALUE_DESERIALIZER_CLASS, KafkaAvroDeserializer.class);
props.put("schema.registry.url", "http://127.0.0.1:8081");
props.put("specific.avro.reader", "true");
props.put("isolation.level", "read_committed");
// props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false"); // disable auto commit of offsets
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, "100"); // disable auto commit of offsets
return props;
}
}
@Component
@Slf4j
public class KafkaConsumerService {
@KafkaListener(id = "demo-consumer-stream-group", topics = "customer.txn")
@Transactional
public void process(ConsumerRecord<String, Customer> record) {
LOGGER.info("Customer key: {} and value: {}", record.key(), record.value());
LOGGER.info("topic: {}, partition: {}, offset: {}", record.topic(), record.partition(), record.offset());
}
}
@Slf4j
@配置
@使能卡夫卡
公共类卡夫卡配置{
@豆子
ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory(){
ConcurrentKafkListenerContainerFactory=新ConcurrentKafkListenerContainerFactory();
setConsumerFactory(consumerFactory());
setAfterRollbackProcessor(新的DefaultAfterRollbackProcessor(-1));
//请参阅OCURREnterrorHandler errorHandler=
//新的SeekToCurInterrorHandler((记录,异常)->{
////3次失败后恢复-例如发送到死信主题
////info(“***在错误处理程序数据中,{}”,记录);
////LOGGER.info(“***在错误处理程序头中,{}”,record.headers());
////LOGGER.info(“值:{}”,新字符串(record.headers().headers(“springDeserializerExceptionValue”).iterator().next().value());
// }, 3);
//
//工厂.setErrorHandler(errorHandler);
返回工厂;
}
@豆子
公共消费者工厂消费者工厂(){
返回新的DefaultKafkanConsumerFactory(consumerConfigs());
}
@豆子
公共地图使用者配置(){
Map props=newhashmap();
put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,“localhost:9092”);
//put(ConsumerConfig.KEY\u反序列化程序\u类\u配置,StringDeserializer.CLASS);
//put(ConsumerConfig.VALUE\反序列化器\类\配置,kafkavrodeserializer.CLASS);
put(ConsumerConfig.KEY\u反序列化程序\u类\u配置,StringDeserializer.CLASS);
put(ConsumerConfig.VALUE\u反序列化程序\u CLASS\u配置,ErrorHandlingDeserializer2.CLASS);
props.put(ErrorHandlingDeserializer2.VALUE_U反序列化器_U类,Kafkaavroderizer.CLASS);
put(“schema.registry.url”http://127.0.0.1:8081");
props.put(“specific.avro.reader”、“true”);
道具放置(“隔离级别”、“读取承诺”);
//props.put(ConsumerConfig.GROUP\u ID\u CONFIG,GROUP
/**
* Execute some arbitrary operation(s) on the operations and return the result.
* The operations are invoked within a local transaction and do not participate
* in a global transaction (if present).
* @param callback the callback.
* @param <T> the result type.
* @return the result.
* @since 1.1
*/
<T> T executeInTransaction(OperationsCallback<K, V, T> callback);