kafka学习笔记(七) --- 幂等生产者和事务生产者

  • 问题引出

这次分享的主题是关于:kafka消息交付可靠性保障和精确一次处理语义的实现。

所谓的消息交付可靠性保障是指kafka对Producer 和 Consumer要处理的消息提供什么样的承诺。一般有以下三种承诺:

  1. 最多一次,消息可能会丢失,但不会被重复发送;
  2. 至少一次,消息不会丢失,但有可能重复发送;
  3. 精确一次,消息不会丢失,也不会被重复发送;

目前kafka默认提供的交付可靠性保障是第二种,即至少一次。正常的“消息已提交”,即只有broker成功提交消息且 producer接到broker的应答才会认为该消息成功发送。但是如果producer没有成功接到broker应答(比如网络抖动原因),那么producer无法确认消息是否真的提交成功,只能选择重试。这就是kafka默认提供至少一次可靠性保障的原因。,不过会导致消息重复发送。当然kafka也可以提供最多一次交付保障,只需要禁止producer充重试即可。我们通常不会希望消息丢失,但有一些场景中偶发的消息丢失其实是被允许的,相反消息重复是要绝对避免的。

但是无论是最多一次还是至少一次,都不如精确一次有吸引力,大部分人还是希望消息指交付一次。这样,消息既不会丢失也不会重复,或者说producer重复发送了相同消息,broker端也能自动去重,在下游看来,消息只有一条。那么,kafka是怎么做到精确一次的呢?通过两种机制:幂等性和事务性。

  • 幂等性

“幂等”这个词原是数学领域的概念,指的是某些操作或函数能够执行多次,但每次得到的结果是不变的。比如乘法运算,数字乘以1就是一个幂等操作,再比如,取整函数(floor和ceiling)是幂等函数,相反如果让数字加1就不是幂等的。在计算机领域,幂等含义稍有点不同,在命令式编程语言中(比如C),若一个子程序是幂等的,那他必然不能修改系统状态,不管运行多少次这个子程序,与该子程序关联的那部分系统状态保持不变;在函数式编程语言中(比如Scala或Haskell),很多纯函数天然是幂等的,不执行任何的side effect。幂等性最大的优势在于我们可以安全地重试任何幂等性操作,反正也不会破坏我们的系统状态,不像非幂等性操作,就需要担心某些操作执行多次对系统状态的影响。

在kafka中,Producer默认不是幂等性的,但是我们可以创建幂等性Producer,这是在0.11.0.0中引入的新功能,就是将参数enable.idempotence设置成true,这样Producer就能自动升级成幂等性Producer,kafka会自动帮你做消息去重。底层原理也很简单,就是经典的空间换时间的优化思路,即在broker端多保存一些字段。当producer发送了具有相同字段值的消息,broker端自动知晓这些消息已重复,再“丢弃”。虽然幂等性Producer使用起来很简单,但我们必须了解它的作用范围。

首先,它只能保证单分区幂等性,即一个消息在某个主题的一个分区上不会出现重复,不法保证多个分区的幂等性。其次,它只能实现单会话的幂等性,不能实现跨会话的幂等性,这里的回话指的是Producer进程的一次运行,如果重启Producer,这种幂等性保证就丧失了。那么,如果想实现多分区和跨会话的消息无重复,那就得以来事务或者事务性Producer。这也是幂等性Producer和事务性Producer最大区别。

  • 事务性

kafka的事务概念和数据库的提供的事务有些类似。在数据库领域,事务提供的安全性保障就是经典的ACID,即原子性、一致性、隔离性、持久性。通常来说,隔离性表明并发执行事务彼此独立,互不影响。即每个事务都假装是数据库中唯一事务。提到隔离级别,很多数据库厂商也都有自己的理解,比如有的提供snapshot隔离级别,有的又称他们为可重复读。好在已提交读(read commited),各个厂商的提法还是一样的。所谓的已提交读,指的是当读取数据库时,只能看到已提交的数据,即无脏读。同时,当写入数据库时,只能覆盖掉已提交数据,即无脏写。

kafka自0.11版本开始也提供了对事物的支持,目前主要是在read commited隔离级别上做事情。他能保证多条消息原子性的写入到目标分区,同时也能保证Consumer只能看到事务成功提交的消息。

事务性Producer能够保证消息原子性写入多个分区,要么全部成功,要么全部失败。另外,事务性Producer也不惧进程重启,kafka依然能保证它们发送消息的精确一次处理。设置事务性Producer也很简单,满足两个要求即可,一是开启enable.idempotence=true;一是设置Producer参数transactional.id,最好是有意义的名字。然后在代码中多一些调整:

                                            

它们分别对应事务的初始化、开始、发送、提交、终止。这段代码能够保证record1和record2被当做一个事物提交到kafka。实际上即使写失败,卡夫卡也会把他们写入到底层日志中,也就是consumer还是会看到这些消息,因此在consumer端,读取事务性Producer发送的消息也需要变更。设置isolation.level参数值即可,当前有两个取值:read_commited和read_uncommited.

read_uncommited: 默认值,表明consumer能够读取kafka写的任何消息,很显然,如果你用了事务性Producer,那么对应的Consumer就不要使用这个值。

read_commited: 只会读取事务性Producer成功提交的消息。当然也会读取非事务性Producer写入的所有消息。

  • 总结

幂等性和事务性Producer都是社区力图为实现精确一次处理语义提供的工具,只是作用范围不同。幂等性Producer只能保证单分区、单会话的消息幂等性;事务性Producer能保证跨分区、跨会话的幂等性。从交付语义来侃,自然是事务性Producer做得更多。不过,天下没有免费的午餐,比起幂等性Producer,事务性Producer性能更差,在实际使用时,要仔细评估一如食物的开销,不要无脑的开启事务。事务更多的用在Kafka Streams中。

需要注意的:对于consumer端,由于偏移量的提交和消息处理的顺序有前有后,依然可能导致重复消费或者消息丢失,如果要实现消费者端的精确一次,还需要通过额外机制在消费者端实现偏移量提交和消息消费的事务处理。

如果想深入了解幂等性Producer设计原理,参考https://www.cnblogs.com/huxi2b/p/7717775.html

标注:这个系列文章是本人在极客时间专栏---kafka核心技术与实战中的学习笔记

    https://time.geekbang.org/column/article/101171

发布了37 篇原创文章 · 获赞 20 · 访问量 4958

猜你喜欢

转载自blog.csdn.net/qq_24436765/article/details/102476803
今日推荐