事务消息的支持与实现(本地事务)

      Rocketmq是保障本地事务(比如数据库)与mq消息发送的事务一致性,Kafka的事务主要是保障一次发送多条消息的事务一致性(要么同时成功要么同时失败)。

    kafka需要对一个topic里的消息做不同的流式计算处理,处理完分别发到不同的topic,这些topic分别被不同的下游系统消费(比如hbase,redis,es等),肯定希望系统发送到多个topic的数据保持事务一致性。

      

spring.kafka.bootstrap-servers=192.168.21.120:9092
spring.kafka.producer.acks=1
spring.kafka.producer.retries=3
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer
# 事务编号前缀
spring.kafka.producer.transaction-id-prefix=transaction-
spring.kafka.consumer.auto-offset-reset=earliest
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer
# 读取已提交的消息
spring.kafka.consumer.isolation-level=read_committed
spring.kafka.consumer.properties.spring.json.trusted.packages=com.smile.domain
# poll 一次拉取的阻塞的最大时长,单位:毫秒。这里指的是阻塞拉取需要满足至少 fetch-min-size 大小的消息
spring.kafka.consumer.fetch-max-wait=10000
# poll 一次消息拉取的最小数据量,单位:字节
spring.kafka.consumer.fetch-min-size=10
# poll 一次消息拉取的最大数量
spring.kafka.consumer.max-poll-records=100
#消费监听接口监听的主题不存在时,默认会报错。所以通过设置为 false ,解决报错
spring.kafka.listener.missing-topics-fatal=false
# 监听器类型,默认为SINGLE ,只监听单条消息。这里我们配置 BATCH ,监听多条消息,批量消费
#spring.kafka.listener.type=batch
logging.level.org.springframework.kafka=ERROR
logging.level.org.apache.kafka=ERROR

     spring.kafka.producer.acks 配置为all,Kafka 的事务消息需要基于幂等性来实现,必须保证所有节点都写入成功,否则的话启动时会抛出Must set acks to all in order to use the idempotent producer. Otherwise we cannot guarantee idempotence

  spring.kafka.consumer.properties.isolation.level 设置为 read_committed ,Consumer 仅读取已提交的消息, 否则不生效

public String testTransaction() {
        return kafkaTemplate.executeInTransaction(new KafkaOperations.OperationsCallback<String, Object, String>() {
            @Override
            public String doInOperations(KafkaOperations<String, Object> kafkaOperations) {
                for (int i = 0; i < 10; i++) {
                    if (i == 7) {
                        throw new RuntimeException("test exception");
                    }
                    Order order = Order.builder().orderId(Long.valueOf(i)).memberId(10002L).payType("WeChat").payTime(new Date()).build();
                    try {
                        SendResult<String, Object> result = kafkaTemplate.send(ORDER_TOPIC, order).get();
                        log.info(i + "-[doInOperations][发送数据:[{}] 发送结果:[{}]]", order, result);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (ExecutionException e) {
                        e.printStackTrace();
                    }
                }
                return "ok";
            }
        });
    }

        模拟发送10条消息,第7条的时候抛出异常,观察消费者是否能消费前面已经发送的6条 ,如果能消费,那肯定不符合和预期。 因为Kafka的事务主要是保障一次发送多条消息的事务一致性(要么同时成功要么同时失败)。

  调用 kafkaTemplate#executeInTransaction(OperationsCallback<K, V, T> callback) 模板方法,实现在 Kafka 事务中,执行自定义 KafkaOperations.OperationsCallback 操作。

       executeInTransaction(...) 方法中,可以通过 KafkaOperations 来执行发送消息等 Kafka 相关的操作,当然了也可以执行自己的业务逻辑,比如 runnable参数,用于表示本地业务逻辑

       executeInTransaction(...) 方法的开始,会自动动创建 Kafka 的事务,然后执行KafkaOperations 的逻辑。成功,则提交 Kafka 事务;失败,则回滚 Kafka 事务。
消费者

    private static final String COMMENT_GROUP = "comment-group";

    @KafkaListener(topics = ORDER_TOPIC, groupId = COMMENT_GROUP)
    public void onMessage(Order order) {
        log.info("【评论】接受消息内容:{}", order);
    }

测试类

    @Test
    public void testTransaction() throws InterruptedException {
        String result = orderProducer.testTransaction();
        log.info("{}", result);
        Thread.currentThread().join();
    }

 

 

     

posted on 2022-01-12 23:06  溪水静幽  阅读(232)  评论(0)    收藏  举报