原来kafka也有事务啊,再也不担心消息不一致了
前言
现在假定这么一个业务场景,从kafka中的topic获取消息数据,经过一定加工处理后,发送到另外一个topic中,要求整个过程消息不能丢失,也不能重复发送,即实现端到端的Exactly-Once精确一次消息投递。这该如何实现呢?
kafka事务介绍
针对上面的业务场景,kafka已经替我们想到了,在kafka 0.11版本以后,引入了一个重大的特性:幂等性和事务。
幂等性
这里提到幂等性的原因,主要是因为事务的启用必须要先开启幂等性,那么什么是幂等性呢?
幂等性是指生产者无论向kafka broker发送多少次重复的数据,broker 端只会持久化一条,保证数据不会重复。
幂等性通过生产者配置项enable.idempotence=true开启,默认情况下为true。
幂等性实现原理
- 每条消息都有一个主键,这个主键由
<PID, Partition, SeqNumber>组成。
PID:ProducerID,每个生产者启动时,Kafka 都会给它分配一个ID,ProducerID是生产者的唯一标识,需要注意的是,Kafka重启也会重新分配PID。Partition:消息需要发往的分区号。SeqNumber:生产者,他会记录自己所发送的消息,给他们分配一个自增的ID,这个ID就是SeqNumber,是该消息的唯一标识,每发送一条消息,序列号加 1。
- 对于主键相同的数据,kafka 是不会重复持久化的,它只会接收一条。
幂等性缺点
根据幂等性的原理,我们发现它存在下面的缺点:
- 只能保证单分区、单会话内的数据不重复
- kafka 挂掉,重新给生产者分配了
PID,还是有可能产生重复的数据
那么如何实现跨分区、kafka broker重启也能保证不重复呢?这就要使用事务了。
事务
所谓事务,就是要求保证原子性,要么全部成功,要么全部失败。那么具体该如何开启呢?
kafka要想开启事务必须要启用幂等性,即生产者配置enable.idempotence=truekafka生产者需要配置唯一的事务idtransactional.id, 最好为其设置一个有意义的名字。kafka消费端也有一个配置项isolation.level和事务有很大关系。
read_uncommitted:默认值,消费端应用可以看到(消费到)未提交的事务,当然对于已提交的事务也是可见的。read_committed:消费端应用只能消费到提交的事务内的消息。
kafka事务 API
现在我们用java的api来实现一下前面这个“消费-处理-生产“的例子吧。
- 引入依赖
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>3.4.0</version>
</dependency>
- 创建事务的生产者
Properties prodcuerProps = new Properties();
// kafka地址
prodcuerProps.setProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"localhost:9092");
// key序列化
prodcuerProps.setProperty(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// value序列化
prodcuerProps.setProperty(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
// 启用幂等性
producerProps.put("enable.idempotence", "true");
// 设置事务id
producerProps.put("transactional.id", "prod-1");
KafkaProducer<String, String> producer = new KafkaProducer(prodcuerProps);
enable.idempotence配置项目为true- 设置
transactional.id
- 创建事务的消费者
Properties consumerProps = new Properties();
consumerProps.put("bootstrap.servers", "localhost:9092");
consumerProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
consumerProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
consumerProps.put("group.id", "my-group-id");
// 设置consumer手动提交
consumerProps.put("enable.auto.commit", "false");
// 设置隔离级别,读取事务已提交的消息
consumerProps.put("isolation.level", "read_committed");
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(consumerProps);
//订阅主题
consumer.subscribe(Collections.singletonList("topic1"));
enable.auto.commit=false,设置手动提交消费者offset- 设置
isolation.level=read_committed,消费事务已提交的消息
- 核心逻辑
// 初始化事务
producer.initTransactions();
while(true) {
// 拉取消息
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000L));
if(!records.isEmpty()){
// 准备一个 hashmap 来记录:"分区-消费位移" 键值对
HashMap<TopicPartition, OffsetAndMetadata> offsetsMap = new HashMap<>();
// 开启事务
producer.beginTransaction();
try {
// 获取本批消息中所有的分区
Set<TopicPartition> partitions = records.partitions();
// 遍历每个分区
for (TopicPartition partition : partitions) {
// 获取该分区的消息
List<ConsumerRecord<String, String>> partitionRecords = records.records(partition);
// 遍历每条消息
for (ConsumerRecord<String, String> record : partitionRecords) {
// 执行数据的业务处理逻辑
ProducerRecord<String, String> outRecord = new ProducerRecord<>("topic2", record.key(), record.value().toUpperCase());
// 将处理结果写入 kafka
producer.send(outRecord);
}
// 将处理完的本分区对应的消费位移记录到 hashmap 中
long offset = partitionRecords.get(partitionRecords.size() - 1).offset();
// 事务提交的是即将到来的偏移量,这意味着我们需要加 1
offsetsMap.put(partition,new OffsetAndMetadata(offset+1));
}
// 向事务管理器提交消费位移
producer.sendOffsetsToTransaction(offsetsMap,"groupid");
// 提交事务
producer.commitTransaction();
} catch(Exeception e) {
e.printStackTrace();
// 终止事务
producer.abortTransaction();
}
}
}
initTransactions(): 初始化事务beginTransaction(): 开启事务sendOffsetsToTransaction(): 在事务内提交已经消费的偏移量(主要用于消费者)commitTransaction(): 提交事务abortTransaction(): 放弃事务
kafka事务实现原理
kafka事务的实现引入了事务协调器,如下图所示:
- 生产者使用事务必须配置事务id, kafka根据事务id计算分配事务协调器
- 事务协调器返回pid,前面的幂等性中需要
- 开始发送消息到topic中,不过这些消息与普通的消息不同,它们带着一个字段标识自己是事务消息
- 当生产者事务内的消息发送完毕,会向事务协调器发送
commit或abort请求,等待 kafka 响应 - 事务协调器收到请求后先持久化到内置事务主题
__transaction_state中,__transaction_state默认有50个分区,每个分区负责一部分事务。事务划分是根据transactional.id的hashcode值%50,计算出该事务属于哪个分区。 该分区Leader副本所在的broker节点即为这个transactional.id对应的Transaction Coordinator节点,这也是上面第一步中的计算逻辑。 - 事务协调器后台会跟topic通信,告诉它们事务是成功还是失败的。
- 如果是成功,topic会汇报自己已经收到消息,协调者收到主题的回应便确认了事务完成,并持久化这一结果。
- 如果是失败的,主题会把这个事务内的消息丢弃,并汇报给协调者,协调者收到所有结果后再持久化这一信息,事务结束。
- 持久化第6步中的事务成功或者失败的信息, 如果
kafka broker配置max.transaction.timeout.ms之前既不提交也不中止事务,kafka broker将中止事务本身。 此属性的默认值为 15 分钟。
总结
本文讲解了通过kafka事务可以实现端到端的精确一次的消息语义,通过事务机制,KAFKA 实现了对多个 topic 的多个 partition 的原子性的写入,通过一个例子了解了一下如何使用事物。同时也简单介绍了事务实现的原理,它底层必须要依赖kafka的幂等性机制,同时通过类似“二段提交”的方式保证事务的原子性。
欢迎关注个人公众号【JAVA旭阳】交流学习!
原来kafka也有事务啊,再也不担心消息不一致了的更多相关文章
- Kafka科普系列 | Kafka中的事务是什么样子的?
事务,对于大家来说可能并不陌生,比如数据库事务.分布式事务,那么Kafka中的事务是什么样子的呢? 在说Kafka的事务之前,先要说一下Kafka中幂等的实现.幂等和事务是Kafka 0.11.0.0 ...
- 多维度对比5款主流分布式MQ消息队列,妈妈再也不担心我的技术选型了
1.引言 对于即时通讯网来说,所有的技术文章和资料都在围绕即时通讯这个技术方向进行整理和分享,这一次也不例外.对于即时通讯系统(包括IM.消息推送系统等)来说,MQ消息中件间是非常常见的基础软件,但市 ...
- Kafka、RabbitMQ、RocketMQ消息中间件的对比 —— 消息发送性能-转自阿里中间件
引言 分布式系统中,我们广泛运用消息中间件进行系统间的数据交换,便于异步解耦.现在开源的消息中间件有很多,前段时间我们自家的产品 RocketMQ (MetaQ的内核) 也顺利开源,得到大家的关注. ...
- Kafka、RabbitMQ、RocketMQ消息中间件的对比 —— 消息发送性能
引言 分布式系统中,我们广泛运用消息中间件进行系统间的数据交换,便于异步解耦.现在开源的消息中间件有很多,前段时间我们自家的产品 RocketMQ (MetaQ的内核) 也顺利开源,得到大家的关注. ...
- 转:Kafka、RabbitMQ、RocketMQ消息中间件的对比 —— 消息发送性能 (阿里中间件团队博客)
from: http://jm.taobao.org/2016/04/01/kafka-vs-rabbitmq-vs-rocketmq-message-send-performance/ 引言 分布式 ...
- 教会舍友玩 Git (再也不用担心他的学习)
舍友长大想当程序员,我和他爷爷奶奶都可高兴了,写他最喜欢的喜之郎牌Git文章,学完以后,再也不用担心舍友的学习了(狗头)哪里不会写哪里 ~~~ 一 先来聊一聊 太多东西属于,总在用,但是一直都没整理的 ...
- 妈妈再也不用担心别人问我是否真正用过redis了
1. Memcache与Redis的区别 1.1. 存储方式不同 1.2. 数据支持类型 1.3. 使用底层模型不同 2. Redis支持的数据类型 3. Redis的回收策略 4. Redis小命令 ...
- 锋利的js之妈妈再也不用担心我找错钱了
用js实现收银功能. <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <hea ...
- 【阿里云产品公测】离线归档OAS,再也不用担心备份空间了
[阿里云产品公测]离线归档OAS,再也不用担心备份空间了 作者:阿里云用户莫须有3i 1 起步 1.1 初识OAS 啥是OAS,请看官方说明: 引用: 开放归档服务(Open Archive Se ...
- RabbitMQ系列(四)RabbitMQ事务和Confirm发送方消息确认——深入解读
RabbitMQ事务和Confirm发送方消息确认--深入解读 RabbitMQ系列文章 RabbitMQ在Ubuntu上的环境搭建 深入了解RabbitMQ工作原理及简单使用 RabbitMQ交换器 ...
随机推荐
- ArcGIS模型构建器ModelBuilder的使用方法
本文介绍在ArcMap软件中,基于模型构建器(ModelBuilder)完成模型建立与使用的具体方法. 首先,在ArcMap软件中打开"ModelBuilder". 建 ...
- JS - new Function
Function 在JavaScript当中,除了可以使用function或箭头函数定义方法外,还可以使用new Function的形式动态创建函数,此时与eval()方法类似 创建一个不接收参数的方 ...
- 打工人都在用的AI工具(第二期)
更多精彩内容,欢迎关注公众号:数量技术宅,也可添加技术宅个人微信号:sljsz01,与我交流. 上周更新的打工人都在用的AI工具(第一期)收到了小伙伴们的高度好评,于是很多小伙伴们急急忙忙的催更,技术 ...
- 【踩坑系列】发送微信模板消息返回40165 invalid weapp pagepath
1. 踩坑经历 最近做了个需求,需要往公司微信公众号推送一个模板消息,并且点击该消息需要跳转到公司小程序的某个页面. 1.1 拿到模板id 既然是发送模板消息,第一步就需要登录微信公众号后台新建模板消 ...
- 系统论——复杂适应系统CAS(三)
美国的圣塔菲研究所一直是复杂性研究的中心.1994年,约翰·霍兰德在圣菲研究所举办的吴拉姆纪念讲座中做了名为"隐秩序"的著名演进,而后,出版了<隐秩序-适应性造就复杂性> ...
- 迁移学习(TSRP)《Improving Pseudo Labels With Intra-Class Similarity for Unsupervised Domain Adaptation》
论文信息 论文标题:Improving Pseudo Labels With Intra-Class Similarity for Unsupervised Domain Adaptation论文作者 ...
- python入门教程之三编码问题
1编码问题 Python文件中如果未指定编码,在执行过程中会出现报错: !/usr/bin/python print ("你好,世界") 以上程序执行输出结果为: 文件" ...
- [人生感悟]做人、做事的"人生十悟"【转载】
做人.做事.做官,是不少人需要经常面对和正确把握的大问题,处理好了,则健康成长,反之则裹足不前,甚至掉入人生的一个个"陷阱",这其中有规律可循,总结"十悟"可思 ...
- [JavaScript]JS屏蔽浏览器右键菜单/粘贴/复制/剪切/选中 [转载]
前两天在解决一个项目缺陷时,突发感兴趣,了解一下~ 1 JS事件 document.oncontextmenu // 右键菜单 document.onpaste //粘贴 document.oncop ...
- [Linux]查看硬件及操作系统信息
许多的软件产品对硬件及操作系统等环境是有具体要求的,那么这时候如何快速知晓目标机器的目标资源信息是较为频繁的操作. 命令 全部硬件及系统信息 dmidecode (软硬件全部信息) hostnamec ...