kafka 事务介绍

14 阅读3分钟

点击查看原文

事务

kafka 中事务中与关系型数据库中事务类似,事务内消息要么全部发送成功,要么全部发送失败,kafka 事务也包含消费位移的提交。用一句话总结下kafka中事务:

多个生产者的多条消息发送操作和消费者的偏移提交操作当作一个原子单元来处理

事务开启

生产者配置

  • 幂等配置 enable.idempotence 为 true
  • transactional.id 配置
        Properties properties = new Properties();
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
                "192.168.86.132:9092");
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
        properties.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG,true);
        properties.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG,"txid1")

初始化事务

注意 一个kafkaProducer 实例不要多次调用该方法

 kafkaProducer.initTransactions();

可以借助Spring 生命周期初始化相关注解 @PostConstruct方法中执行

    private KafkaProducer kafkaProducer ;


    private Properties getProduceConfig(){
        Properties properties = new Properties();
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
                "192.168.86.132:9092");
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
        properties.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG,true);
        properties.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG,"txid1");

        return properties;
    }


    @PostConstruct
    public void init(){
        kafkaProducer = new KafkaProducer(getProduceConfig());
        kafkaProducer.initTransactions();
    }

发送代码

  • 开启事务  kafkaProducer.beginTransaction()
  • 提交事务 kafkaProducer.commitTransaction()
  • 中止事务 kafkaProducer.abortTransaction()

下面测试代码发送消息数量超过50条会抛异常,用于模拟事务中止场景

    public void sendMessageTx(String topic, List<String> messages) {

        kafkaProducer.beginTransaction();

        try {
            int count =1;
            for (String message : messages) {
                ProducerRecord producerRecord = new ProducerRecord(topic, message);
                kafkaProducer.send(producerRecord, new Callback() {
                    @Override
                    public void onCompletion(RecordMetadata recordMetadata, Exception e) {
                        if (e != null) {
                            e.printStackTrace();
                        } else {
                            System.out.println(message + "  send onCompletion");
                        }
                    }
                });
                if(count % 50 == 0){
                    int a = 1/0;
                }
                count ++;

            }
            kafkaProducer.commitTransaction();
        } catch (Exception ex) {
            ex.printStackTrace();
            kafkaProducer.abortTransaction();
        }

    }

消费者配置

消费者需要配置事务隔离级别,没错kafka事务也有隔离级别,分为读未提交读已提交

  • 读已未提交 read_uncommitted

在此隔离级别下,消费者可以读取包括未提交事务中的消息。

  • 读已提交 read_committed

在这种隔离级别下,消费者只能读取已经成功提交的事务消息。

配置代码如下  消息消费代码参考之前的文章

    private Properties getConfig() {
        Properties properties = new Properties();
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
                "localhost:9092");
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
        properties.put(ConsumerConfig.GROUP_ID_CONFIG,consumerGroup);
        properties.put(ConsumerConfig.ISOLATION_LEVEL_CONFIG,"read_committed");
        return properties;
    }

Kafka 事务原理简述

Kafka 事务的实现基于其内部的一些关键组件和机制,以下是其原理的详细介绍:

事务协调器

事务协调器(Transaction Coordinator) 事务协调器是 Kafka 事务的核心组件,负责管理事务的状态和协调事务相关的操作。每个事务都与一个特定的事务协调器相关联。

事务状态会持久化到内部主题 __transaction_state

两阶段提交

Kafka 事务采用了两阶段提交的协议来确保事务的原子性和一致性。

第一阶段:准备阶段

  • 生产者发送带有事务 ID 的消息,并将这些消息标记为“待提交”。 - 同时,生产者会向事务协调器发送请求,表明准备提交事务。

第二阶段:提交或回滚阶段

  • 事务协调器检查事务相关的所有操作是否都成功完成。 - 如果一切正常,事务协调器通知生产者提交事务,生产者将“待提交”的消息标记为“已提交”。 - 如果在准备阶段出现问题,事务协调器通知生产者回滚事务,生产者丢弃之前标记为“待提交”的消息。

如果是手动提交消息offset, 需要将消息位移提交也加入到事务范围,可以通过调用也加入到事务范围,可以通过调用kafkaProducer.sendOffsetsToTransaction()