Kafka 事务
更多内容,前往IT-BLOG
在了解 Kafka的事务之前,先说一下 Kafka中幂等和事务(Kafka 0.11.0.0版本引入的两个特性)以此来实现 Exactly once(精确一次)了解更多链接。
幂等:生产者在进行重试的时候有可能会重复写入消息,而使用 Kafka的幂等性功能之后就可以避免这种情况。
生产者事务相关配置
开启幂等性功能的方式很简单,只需显式地将生产者客户端参数 enable.idempotence=true(默认值为false)Kafka的幂等只能保证单个生产者会话(session)中单分区的幂等。幂等性不能跨多个分区运作,而事务可以弥补这个缺陷。事务可以保证对多个分区写入操作的原子性。操作的原子性是指多个操作要么全部成功,要么全部失败,不存在部分成功、部分失败的可能。
为了使用事务,Producer 必须显式设置唯一的 transactionalId。事务要求生产者开启幂等性,因此通过将 transactional.id参数设置为非空从而开启事务特性的同时需要将 enable.idempotence设置为true(设置 transactional.id后,enable.idempotence会自动设置为true),如果用户显式地将 enable.idempotence设置为false,则会报出 ConfigException的异常。
transactionalId 与 PID一一对应,两者不同的是 transactionalId由用户显式设置,而 PID是由 Kafka内部分配的。
拒绝僵尸实例(Zombie fencing):为了保证新的生产者启动后具有相同 transactionalId的旧生产者能够立即失效,每个生产者通过 transactionalId获取 PID的同时,还会获取一个单调递增的 producer epoch。如果使用同一个 transactionalId开启两个生产者,Kafka收到事务提交请求时检查当前事务提交者的 epoch不是最新的,那么就会拒绝该 Producer的请求。从而达成拒绝僵尸实例的目标。
Kafka中的事务特性主要用于以下两种场景:
【1】生产者发送多条消息可以封装在一个事务中,形成一个原子操作。多条消息要么都发送成功,要么都发送失败。
【2】read-process-write模式:将消息消费和生产封装在一个事务中,形成一个原子操作。在一个流式处理的应用中,常常一个服务需要从上游接收消息,然后经过处理后送达到下游,这就对应着消息的消费和生成。
从生产者的角度分析,通过事务,Kafka可以保证跨生产者会话的消息幂等发送,以及跨生产者会话的事务恢复。前者表示具有相同 transactionalId的新生产者实例被创建且工作的时候,旧的且拥有相同 transactionalId的生产者实例将不再工作。后者指当某个生产者实例宕机后,新的生产者实例可以保证任何未完成的旧事务要么被提交(Commit),要么被中止(Abort),如此可以使新的生产者实例从一个正常的状态开始工作。KafkaProducer提供了5个与事务相关的方法,详细如下:
1 /**
2 * 初始化事务
3 */
4 public void initTransactions();
5 /**
6 * 开启事务
7 */
8 public void beginTransaction() throws ProducerFencedException ;
9 /**
10 * 在事务内提交已经消费的偏移量
11 */
12 public void sendOffsetsToTransaction(Map<TopicPartition, OffsetAndMetadata> offsets,
13 String consumerGroupId) throws ProducerFencedException ;
14 /**
15 * 提交事务
16 */
17 public void commitTransaction() throws ProducerFencedException;
18 /**
19 * 丢弃事务
20 */
21 public void abortTransaction() throws ProducerFencedException ;
initTransactions()方法用来初始化事务;
beginTransaction()方法用来开启事务;
sendOffsetsToTransaction()方法为消费者提供在事务内的位移提交的操作;
commitTransaction()方法用来提交事务;
abortTransaction()方法用来中止事务,类似于事务回滚;
下面是使用 Kafka事务特性的例子,这段代码 Producer开启了一个事务,然后在这个事务中发送了两条消息。这两条消息要么都发送成功,要么都失败。
1 KafkaProducer producer = createKafkaProducer( "bootstrap.servers", "localhost:9092", "transactional.id”, “my-transactional-id");
2 producer.initTransactions();
3 producer.beginTransaction();
4 producer.send("outputTopic", "message1");
5 producer.send("outputTopic", "message2");
6 producer.commitTransaction();
下面这段代码即为 read-process-write模式,在一个 Kafka事务中,同时涉及到了生产消息和消费消息。
1 KafkaProducer producer = createKafkaProducer(
2 "bootstrap.servers", "localhost:9092",
3 "transactional.id", "my-transactional-id");
4
5 KafkaConsumer consumer = createKafkaConsumer(
6 "bootstrap.servers", "localhost:9092",
7 "group.id", "my-group-id",
8 "isolation.level", "read_committed");
9
10 consumer.subscribe(singleton("inputTopic"));
11
12 producer.initTransactions();
13
14 while (true) {
15 ConsumerRecords records = consumer.poll(Long.MAX_VALUE);
16 producer.beginTransaction();
17 for (ConsumerRecord record : records)
18 producer.send(producerRecord(“outputTopic”, record));
19 producer.sendOffsetsToTransaction(currentOffsets(consumer), group);
20 producer.commitTransaction();
21 }
注意:在理解消息的事务时,一直处于一个错误理解是,把操作 db的业务逻辑跟操作消息当成是一个事务,如下所示:
1 void kakfa_in_tranction(){
2 // 1.kafa的操作:读取消息或生产消息
3 kafkaOperation();
4 // 2.db操作
5 dbOperation();
6 }
其实这个是有问题的。操作 DB数据库的数据源是DB,消息数据源是kfaka,这是完全不同两个数据。一种数据源(如mysql,kafka)对应一个事务,所以它们是两个独立的事务。kafka事务指 kafka一系列生产、消费消息等操作组成一个原子操作,db事务是指操作数据库的一系列增删改操作组成一个原子操作。
消费者事务相关配置
在消费端有一个参数isolation.level,与事务有关,这个参数的默认值为“read_uncommitted”,意思是说消费端应用可以看到(消费到)未提交的事务,当然对于已提交的事务也是可见的。这个参数还可以设置为“read_committed”,表示消费端应用不可以看到尚未提交的事务内的消息。另外,需要设置 enable.auto.commit = false来关闭自动提交 Offset功能。
举个例子,如果生产者开启事务并向某个分区值发送3条消息msg1、msg2和msg3,在执行 commitTransaction()或abortTransaction()方法前,设置为 “read_committed”的消费端应用是消费不到这些消息的,不过在 KafkaConsumer内部会缓存这些消息,直到生产者执行 commitTransaction()方法之后它才能将这些消息推送给消费端应用。反之,如果生产者执行了abortTransaction()方法,那么 KafkaConsumer会将这些缓存的消息丢弃而不推送给消费端应用。
日志文件中除了普通的消息,还有一种消息专门用来标志一个事务的结束,它就是控制消息(ControlBatch)。控制消息一共有两种类型:COMMIT 和 ABORT,分别用来表征事务已经成功提交或已经被成功中止。RecordBatch 中 attributes字段的第6位用来标识当前消息是否是控制消息。如果是控制消息,那么这一位会置为1,否则会置为0,如上图所示。attributes字段中的第5位用来标识当前消息是否处于事务中,如果是事务中的消息,那么这一位置为1,否则置为0。由于控制消息也处于事务中,所以attributes字段的第5位和第6位都被置为1。
KafkaConsumer可以通过这个控制消息来判断对应的事务是被提交了还是被中止了,然后结合参数 isolation.level配置的隔离级别来决定是否将相应的消息返回给消费端应用,如上图所示。注意 ControlBatch对消费端应用不可见。
Kafka 事务原理
Kafka为了支持事务特性,引入一个新的组件:Transaction Coordinator。主要负责分配pid,记录事务状态等操作。下面时Kafka开启一个事务到提交一个事务的流程图:
主要分为以下步骤:
【1】查找 Tranaction Corordinator:Producer向任意一个 Brokers发送 FindCoordinatorRequest请求来获取 Transaction Coordinator的地址。
【2】初始化事务 initTransaction:Producer发送 InitpidRequest 给 Transaction Coordinator,获取pid。Transaction Coordinator 在 Transaciton Log中记录这 <TransactionId,pid> 的映射关系。另外,它还会做两件事:
- 恢复(Commit 或 Abort)之前的 Producer未完成的事务
- 对 PID对应的 epoch进行递增,这样可以保证同一个 app的不同实例对应的 PID是一样,而 epoch是不同的。
只要开启了幂等性即必须执行 InitpidRequest,而无须考虑该 Producer是否开启了事务特性。
【3】开始事务beginTransaction:执行 Producer的 beginTransacion(),它的作用是 Producer在本地记录下这个 transaction的状态为开始状态。这个操作并没有通知 Transaction Coordinator,因为 Transaction Coordinator只有在 Producer发送第一条消息后才认为事务已经开启。
【4】read-process-write流程:一旦 Producer开始发送消息,Transaction Coordinator会将该<Transaction, Topic, Partition>存于 Transaction Log内,并将其状态置为 BEGIN。另外,如果该 <Topic, Partition>为该事务中第一个 <Topic, Partition>,Transaction Coordinator 还会启动对该事务的计时(每个事务都有自己的超时时间)。
在注册<Transaction, Topic, Partition> 到 Transaction Log后,生产者发送数据,虽然没有还没有执行 commit或者 abort,但是此时消息已经保存到 Broker上了。即使后面执行 abort,消息也不会删除,只是更改状态字段标识消息为 abort状态。
【5】事务提交或终结 commitTransaction/abortTransaction:在 Producer执行 commitTransaction/abortTransaction时,Transaction Coordinator会执行一个两阶段提交:
- 第一阶段,将 Transaction Log内的该事务状态设置为
PREPARE_COMMIT
或PREPARE_ABORT
- 第二阶段,将
Transaction Marker
写入该事务涉及到的所有消息(即将消息标记为committed
或aborted
)。这一步骤Transaction Coordinator会发送给当前事务涉及到的每个<Topic, Partition>的 Leader,Broker收到该请求后,会将对应的Transaction Marker
控制信息写入日志。
一旦 Transaction Marker
写入完成,Transaction Coordinator会将最终的 COMPLETE_COMMIT
或 COMPLETE_ABORT
状态写入Transaction Log中以标明该事务结束。
Kafka 事务的更多相关文章
- Hadoop生态圈-kafka事务控制以及性能测试
Hadoop生态圈-kafka事务控制以及性能测试 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任.
- kafka 事务代码实现(生产者到server端的事务)
kafka的事务指的是2个点 ① 生产者到kafka服务端的事务保障 ②消费者从kafka拉取数据的事务 kafka提供的事务机制是 第①点, 对于第②点来说 只能自己在消费端实现幂等性. ...
- Kafka事务原理剖析
一.事务概览 提起事务,我们第一印象可能就是ACID,需要满足原子性.一致性.事务隔离级别等概念,那kafka的事务能做到什么程度呢?我们首先看一下如何使用事务 Producer端代码如下 Kafka ...
- kafka系列九、kafka事务原理、事务API和使用场景
一.事务场景 最简单的需求是producer发的多条消息组成一个事务这些消息需要对consumer同时可见或者同时不可见 . producer可能会给多个topic,多个partition发消息,这些 ...
- 【干货】Kafka 事务特性分析
特性背景 消息事务是指一系列的生产.消费操作可以要么都完成,要么都失败,类似数据库的事务.这个特性在0.10.2的版本是不支持的,从0.11版本开始才支持.华为云DMS率先提供Kafka 1.1.0的 ...
- kafka事务
Kafka 从 0.11 版本开始引入了事务支持.事务可以保证 Kafka 在 Exactly Once 语义的基 础上,生产和消费可以跨分区和会话,要么全部成功,要么全部失败. 开启幂等性的 Pro ...
- Kafka设计解析(八)- Exactly Once语义与事务机制原理
原创文章,首发自作者个人博客,转载请务必将下面这段话置于文章开头处. 本文转发自技术世界,原文链接 http://www.jasongj.com/kafka/transaction/ 写在前面的话 本 ...
- kafka 幂等生产者及事务(kafka0.11之后版本新特性)
1. 幂等性设计1.1 引入目的生产者重复生产消息.生产者进行retry会产生重试时,会重复产生消息.有了幂等性之后,在进行retry重试时,只会生成一个消息. 1.2 幂等性实现1.2.1 PID ...
- Kafka设计解析(八)Exactly Once语义与事务机制原理
转载自 技术世界,原文链接 Kafka设计解析(八)- Exactly Once语义与事务机制原理 本文介绍了Kafka实现事务性的几个阶段——正好一次语义与原子操作.之后详细分析了Kafka事务机制 ...
- 【Kafka】Exactly Once语义与事务
Kafka在0.11.0.0之前的版本中只支持At Least Once和At Most Once语义,尚不支持Exactly Once语义. 但是在很多要求严格的场景下,如使用Kafka处理交易数据 ...
随机推荐
- memoize
function getArea(r){ console.log(r); return Math.PI * r * r } function memoize(f){ let cache = {}; r ...
- excel的几个常用方法
--笔记开始: 1.if(条件,真值,假值),类似于编程语言中的三元运算符.条件为真时返回真值,条件为假时返回假值. 2.match(目标值,查找区域,查找类型),一般查找类型为0(等值查找),查找区 ...
- centos安装docker显示 No package docker-ce available
温馨提醒,docker内核版本必须是3.10+以上的版本 查看方式 uname -r 1. 卸载老版本的 docker 及其相关依赖 sudo yum remove docker docker-com ...
- 简单了解promise
promise是什么: JavaScript中存在很多异步操作, Promise将异步操作队列化,按照期望的顺序执行,返回 符合预期的结果.可以通过链式调用多个 Promise达到我们的目的. Pro ...
- 华为服务器修改ibmc账号密码、配置raid5、安装系统
修改ibmc账号密码 转载自:https://www.cnblogs.com/mtactor/p/2288V5.html 昵称: mtactor 方法一:采用网线直连管理口 1.使用网线直接连接服务 ...
- heimaJava18_线程
Java 线程 单线程 线程(thread)是一个程序内部的一条执行路径. main方法的执行其实就是一个单独的执行路径 程序中如果只有一条执行路径,那么这个程序就是单线程的程序 多线程 多线程是指从 ...
- 如何用算法把一个十进制数转为十六进制数-C语言基础
这一篇文章要探讨的是"如何用算法实现十进制转十六进制"并不涉及什么特别的知识点.属于C语言基础篇. 在翻找素材的时候,发现一篇以前写的挺有意思的代码,这篇代码里面涉及的知识点没有什 ...
- hihocoder 1066 并查集
/**/ #include <cstdio> #include <cstring> #include <cmath> #include <cctype> ...
- 代码还是那个代码,但我已经知道了hashmap背后的东西
代码还是那个代码,但我已经知道了hashmap背后的东西 数据结构是链表的数组(注:后面的版本为了提升性能,已经是改成链表或者树(节点较多)了) 思想上是空间换时间的算法 构造函数上有容量和负载因子2 ...
- 替换yum源
1.yum源进行备份 进入到yum源的配置文件中 执行命令如下:cd /etc/yum.repos.d 将yum源进行备份:mv Centos-Base.repo Centos-Base.repo.b ...