先看下下面这种情况,程序都出错了,按理说消息也不应该成功
@GetMapping("/send")
public void test9(String message) {
kafkaTemplate.send(topic, message);
throw new RuntimeException("fail");
}
但是执行结果是发生了异常并且消息发送成功了:
Kafka 同数据库一样支持事务,当发生异常的时候可以进行回滚,确保消息监听器不会接收到一些错误的或者不需要的消息。
kafka事务属性是指一系列的生产者生产消息和消费者提交偏移量的操作在一个事务,或者说是是一个原子操作),同时成功或者失败。使用事务也很简单,需要先开启事务支持,然后再使用。
如何开启事务
如果使用默认配置只需要在yml添加spring.kafka.producer.transaction-id-prefix配置来开启事务,之前没有使用默认的配置,自定义的kafkaTemplate,那么需要在ProducerFactory中设置事务Id前缀开启事务并将KafkaTransactionManager注入到spring中,看下KafkaProducerConfig完整代码:
@Configuration
@EnableKafka
public class KafkaProducerConfig {
@Value("${kafka.producer.servers}")
private String servers;
@Value("${kafka.producer.retries}")
private int retries;
public Map<String,Object> producerConfigs(){
Map<String,Object> props = new HashMap<>();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, servers);
props.put(ProducerConfig.RETRIES_CONFIG,retries);
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
// 配置分区策略
props.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,"com.example.springbootkafka.config.CustomizePartitioner");
// 配置生产者拦截器
props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG,"com.example.springbootkafka.interceptor.CustomProducerInterceptor");
// 配置拦截器消息处理类
SendMessageInterceptorUtil sendMessageInterceptorUtil = new SendMessageInterceptorUtil();
props.put("interceptorUtil",sendMessageInterceptorUtil);
return props;
}
@Bean
public ProducerFactory<String,String> producerFactory(){
DefaultKafkaProducerFactory producerFactory = new DefaultKafkaProducerFactory(producerConfigs());
//设置事务Id前缀 开启事务
producerFactory.setTransactionIdPrefix("tx-");
return producerFactory;
}
@Bean
public KafkaTemplate<String,String> kafkaTemplate(){
return new KafkaTemplate<>(producerFactory());
}
@Bean
public KafkaTransactionManager<Integer, String> kafkaTransactionManager(ProducerFactory<String, String> producerFactory) {
return new KafkaTransactionManager(producerFactory);
}
}
配置开启事务后,使用大体有两种方式,先记录下第一种使用事务方式:使用 executeInTransaction 方法
直接看下代码:
@GetMapping("/send11")
public void test11(String message) {
kafkaTemplate.executeInTransaction(operations ->{
operations.send(topic,message);
throw new RuntimeException("fail");
});
}
当然你可以这么写:
@GetMapping("/send11")
public void test11(String message) {
kafkaTemplate.executeInTransaction(new KafkaOperations.OperationsCallback(){
@Override
public Object doInOperations(KafkaOperations operations) {
operations.send(topic,message);
throw new RuntimeException("fail");
}
});
}
启动项目,访问http://localhost:8080/send10?message=test10 结果如下:
如上:消费者没打印消息,说明消息没发送成功,并且前面会报错org.apache.kafka.common.KafkaException: Failing batch since transaction was aborted 的错误,说明事务生效了。
第一种使用事务方式:使用 @Transactional 注解方式 直接在方法上加上@Transactional注解即可,看下代码:
@GetMapping("/send12")
@Transactional
public void test12(String message) {
kafkaTemplate.send(topic, message);
throw new RuntimeException("fail");
}
如果开启的事务,则后续发送消息必须使用@Transactional注解或者使用kafkaTemplate.executeInTransaction() ,否则抛出异常,异常信息如下:
贴下完整的异常吧:java.lang.IllegalStateException: No transaction is in process; possible solutions: run the template operation within the scope of a template.executeInTransaction() operation, start a transaction with @Transactional before invoking the template method, run in a transaction started by a listener container when consuming a record