事务消息的支持与实现(本地事务)
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(); }
浙公网安备 33010602011771号