【Kafka】Exactly Once语义与事务
Kafka在0.11.0.0之前的版本中只支持At Least Once
和At Most Once
语义,尚不支持Exactly Once
语义。
但是在很多要求严格的场景下,如使用Kafka处理交易数据,Exactly Once
语义是必须的。我们可以通过让下游系统具有幂等性来配合Kafka的At Least Once
语义来间接实现Exactly Once
。但是:
- 该方案要求下游系统支持幂等操作,限制了Kafka的适用场景
- 实现门槛相对较高,需要用户对Kafka的工作机制非常了解
- 对于Kafka Stream而言,Kafka本身即是自己的下游系统,但Kafka在0.11.0.0版本之前不具有幂等发送能力
因此,Kafka本身对Exactly Once
语义的支持就非常必要。
操作原子性
操作的原子性是指,多个操作要么全部成功要么全部失败,不存在部分成功部分失败的可能。
实现原子性操作的意义在于:
- 操作结果更可控,有助于提升数据一致性
- 便于故障恢复。因为操作是原子的,从故障中恢复时只需要重试该操作(如果原操作失败)或者直接跳过该操作(如果原操作成功),而不需要记录中间状态,更不需要针对中间状态作特殊处理
实现事务机制的几个阶段
幂等性发送
上文提到,实现Exactly Once
的一种方法是让下游系统具有幂等处理特性,而在Kafka Stream中,Kafka Producer本身就是“下游”系统,因此如果能让Producer具有幂等处理特性,那就可以让Kafka Stream在一定程度上支持Exactly once
语义。
为了实现Producer的幂等语义,Kafka引入了Producer ID
(即PID
)和Sequence Number
。每个新的Producer在初始化的时候会被分配一个唯一的PID,该PID对用户完全透明而不会暴露给用户。
对于每个PID,该Producer发送数据的每个<Topic, Partition>
都对应一个从0开始单调递增的Sequence Number
。
类似地,Broker端也会为每个<PID, Topic, Partition>
维护一个序号,并且每次Commit一条消息时将其对应序号递增。对于接收的每条消息,如果其序号比Broker维护的序号(即最后一次Commit的消息的序号)大一,则Broker会接受它,否则将其丢弃:
- 如果消息序号比Broker维护的序号大一以上,说明中间有数据尚未写入,也即乱序,此时Broker拒绝该消息,Producer抛出
InvalidSequenceNumber
- 如果消息序号小于等于Broker维护的序号,说明该消息已被保存,即为重复消息,Broker直接丢弃该消息,Producer抛出
DuplicateSequenceNumber
上述设计解决了0.11.0.0之前版本中的两个问题:
- Broker保存消息后,发送ACK前宕机,Producer认为消息未发送成功并重试,造成数据重复
- 前一条消息发送失败,后一条消息发送成功,前一条消息重试后成功,造成数据乱序
事务性保证
上述幂等设计只能保证单个Producer对于同一个<Topic, Partition>
的Exactly Once
语义。
另外,它并不能保证写操作的原子性——即多个写操作,要么全部被Commit要么全部不被Commit。
更不能保证多个读写操作的的原子性。尤其对于Kafka Stream应用而言,典型的操作即是从某个Topic消费数据,经过一系列转换后写回另一个Topic,保证从源Topic的读取与向目标Topic的写入的原子性有助于从故障中恢复。
事务保证可使得应用程序将生产数据和消费数据当作一个原子单元来处理,要么全部成功,要么全部失败,即使该生产或消费跨多个<Topic, Partition>
。
另外,有状态的应用也可以保证重启后从断点处继续处理,也即事务恢复。
为了实现这种效果,应用程序必须提供一个稳定的(重启后不变)唯一的ID,也即Transaction ID
。Transactin ID
与PID
可能一一对应。区别在于Transaction ID
由用户提供,而PID
是内部的实现对用户透明。
另外,为了保证新的Producer启动后,旧的具有相同Transaction ID
的Producer即失效,每次Producer通过Transaction ID
拿到PID的同时,还会获取一个单调递增的epoch。由于旧的Producer的epoch比新Producer的epoch小,Kafka可以很容易识别出该Producer是老的Producer并拒绝其请求。
有了Transaction ID
后,Kafka可保证:
- 跨Session的数据幂等发送。当具有相同
Transaction ID
的新的Producer实例被创建且工作时,旧的且拥有相同Transaction ID
的Producer将不再工作。 - 跨Session的事务恢复。如果某个应用实例宕机,新的实例可以保证任何未完成的旧的事务要么Commit要么Abort,使得新实例从一个正常状态开始工作。
事务机制原理
事务性消息传递
这一节所说的事务主要指原子性,也即Producer将多条消息作为一个事务批量发送,要么全部成功要么全部失败。
为了实现这一点,Kafka 0.11.0.0引入了一个服务器端的模块,名为Transaction Coordinator
,用于管理Producer发送的消息的事务性。
该Transaction Coordinator
维护Transaction Log
,该log存于一个内部的Topic内。由于Topic数据具有持久性,因此事务的状态也具有持久性。
Producer并不直接读写Transaction Log
,它与Transaction Coordinator
通信,然后由Transaction Coordinator
将该事务的状态插入相应的Transaction Log
。
Transaction Log
的设计与Offset Log
用于保存Consumer的Offset类似。
事务中Offset的提交
许多基于Kafka的应用,尤其是Kafka Stream应用中同时包含Consumer和Producer,前者负责从Kafka中获取消息,后者负责将处理完的数据写回Kafka的其它Topic中。
为了实现该场景下的事务的原子性,Kafka需要保证对Consumer Offset的Commit与Producer对发送消息的Commit包含在同一个事务中。否则,如果在二者Commit中间发生异常,根据二者Commit的顺序可能会造成数据丢失和数据重复:
- 如果先Commit Producer发送数据的事务再Commit Consumer的Offset,即
At Least Once
语义,可能造成数据重复。 - 如果先Commit Consumer的Offset,再Commit Producer数据发送事务,即
At Most Once
语义,可能造成数据丢失。
总结
PID
与Sequence Number
的引入实现了写操作的幂等性- 写操作的幂等性结合
At Least Once
语义实现了单一Session内的Exactly Once
语义 Transaction Marker
与PID
提供了识别消息是否应该被读取的能力,从而实现了事务的隔离性- Offset的更新标记了消息是否被读取,从而将对读操作的事务处理转换成了对写(Offset)操作的事务处理
- Kafka事务的本质是,将一组写操作(如果有)对应的消息与一组读操作(如果有)对应的Offset的更新进行同样的标记(即
Transaction Marker
)来实现事务中涉及的所有读写操作同时对外可见或同时对外不可见 - Kafka只提供对Kafka本身的读写操作的事务性,不提供包含外部系统的事务性
出处:http://www.jasongj.com/kafka/transaction/
【Kafka】Exactly Once语义与事务的更多相关文章
- Kafka设计解析(八)- Exactly Once语义与事务机制原理
原创文章,首发自作者个人博客,转载请务必将下面这段话置于文章开头处. 本文转发自技术世界,原文链接 http://www.jasongj.com/kafka/transaction/ 写在前面的话 本 ...
- Kafka设计解析(八)Exactly Once语义与事务机制原理
转载自 技术世界,原文链接 Kafka设计解析(八)- Exactly Once语义与事务机制原理 本文介绍了Kafka实现事务性的几个阶段——正好一次语义与原子操作.之后详细分析了Kafka事务机制 ...
- kafka 幂等生产者及事务(kafka0.11之后版本新特性)
1. 幂等性设计1.1 引入目的生产者重复生产消息.生产者进行retry会产生重试时,会重复产生消息.有了幂等性之后,在进行retry重试时,只会生成一个消息. 1.2 幂等性实现1.2.1 PID ...
- 基于Kafka消息驱动最终一致事务(二)
实现用例分析 上篇基于Kafka消息驱动最终一致事务(一)介绍BASE的理论,接着我们引入一个实例看如何实现BASE,我们会用图7显示的算法实现BASE.
- 基于Kafka消息驱动最终一致事务(一)
基本可用软状态最终一致事务 本用例分两个数据库分别是用户库和交易库,不使用分布式事务,使用基于消息驱动实现基本可用软状态最终一致事务(BASE).现在说明下事务逻辑演化步骤,尊从CAP原则,即分布式系 ...
- Kafka 幂等生产者和事务生产者特性(讨论基于 kafka-python | confluent-kafka 客户端)
Kafka 提供了一个消息交付可靠性保障以及精确处理一次语义的实现.通常来说消息队列都提供多种消息语义保证 最多一次 (at most once): 消息可能会丢失,但绝不会被重复发送. 至少一次 ( ...
- 使用kafka消息队列解决分布式事务(可靠消息最终一致性方案-本地消息服务)
微服务框架Spring Cloud介绍 Part1: 使用事件和消息队列实现分布式事务 本文转自:http://skaka.me/blog/2016/04/21/springcloud1/ 不同于单一 ...
- Hibernate(四)结构-基础语义和事务
一.基础语义 核心: Configuration SessionFactory Session 二.Configuration Configuration类负责管理Hibernate的配置信息,Hib ...
- Kafka集群的安装和使用
Kafka是一种高吞吐量的分布式发布订阅的消息队列系统,原本开发自LinkedIn,用作LinkedIn的活动流(ActivityStream)和运营数据处理管道(Pipeline)的基础.现在它已被 ...
随机推荐
- arXiv上传文章latex源码技巧
<<2019.09.27>>更新 上传PS文件看来也是不行了,一大早收到邮件被arXiv标记为incomplete了.哎,还是老老实实提交Latex source files吧 ...
- Flex弹性盒模型(新老版本完整)--移动端开发整理笔记(二)
Flex布局 Flex即Flexible Box,写法为:display:flex(旧版:display: -webkit-box) 在Webkit内核下,需要加-webkit前缀: .box{ di ...
- 使用plv8+hashids生成短链接服务
有写过一个集成npm plv8 以及shortid生成短链接id服务,实际上我们可以集成触发器自动生成url对应的短链接地址,hashids也是一个不错的选择. 以下是一个别人写的一个博客实现可以参考 ...
- cf1199解题报告
目录 cf1199解题报告 A B C D E F cf1199解题报告 发一波水题. A 模拟 #include <bits/stdc++.h> #define ll long long ...
- nodejs网络编程
通过NodeJS,除了可以编写一些服务端程序来协助前端开发和测试外,还能够学习一些HTTP协议与Socket协议的相关知识,这些知识在优化前端性能和排查前端故障时说不定能派上用场.本章将介绍与之相关的 ...
- docker 创建私有镜像之 registry
一.下载 registry 镜像 [root@localhost ~]# docker pull registry Using default tag: latest latest: Pulling ...
- Set和Multiset 怎么用咧↓↓↓
转自:[C++ STL]Set和Multiset - Memset - 博客园https://www.cnblogs.com/ChinaHook/p/6985444.html (对字体进行了略微的修改 ...
- 疯了!同事又问我为什么不能用 isXXX
最近在做Code Review,写下了这篇文章:代码写成这样,老夫无可奈何!,说多了都是泪啊.. 最近又有人同事跑过来质疑我: 为什么变量名取名不能用 isXXX 这种方式,这样有什么问题?! 醉了, ...
- Hotspot的Metaspace
Meta Space是JDK1.8引入的,在JDK1.8使用的是方法区,永久代(Permnament Generation).元空间存储的是元信息,使用的是操作系统的本地内存(Metaspace与Pe ...
- 微信小程序之判断页面来源
1. 对非首页,使用 getCurrentPages 函数获取当前页面栈 onLoad: function (options) { let pages = getCurrentPages() if ( ...