Kafka设计解析(三)恰好一次和事务消息

1.幂等消息

为了解决重试导致的消息重复、乱序问题,kafka引入了幂等消息。幂等消息保证producer在一次会话内写入一个partition内的消息具有幂等性,可以通过重试来确保消息发布的Exactly Once语义。

实现逻辑很简单:

  • 区分producer会话

producer每次启动后,首先向broker申请一个全局唯一的pid,用来标识本次会话。

  • 消息检测

message_v2 增加了sequence number字段,producer每发一批消息,seq就加1。

broker在内存维护(pid,seq)映射,收到消息后检查seq,如果,

new_seq=old_seq+1: 正常消息;

new_seq<=old_seq : 重复消息;

new_seq>old_seq+1: 消息丢失;
  • producer重试

producer在收到明确的的消息丢失ack,或者超时后未收到ack,要进行重试。

2.事务消息

考虑在stream处理的场景中,需要多个消息的原子写入语义,要么全部写入成功,要么全部失败,这就是kafka事务消息要解决的问题。

事务消息是由producer、事务协调器、broker、组协调器、consumer共同参与实现的,

1)producer

为producer指定固定的TransactionalId,可以穿越producer的多次会话(producer重启/断线重连)中,持续标识producer的身份。

使用epoch标识producer的每一次"重生",防止同一producer存在多个会话。

producer遵从幂等消息的行为,并在发送的recordbatch中增加事务id和epoch。

2)事务协调器(Transaction Coordinator)

引入事务协调器,以两阶段提交的方式,实现消息的事务提交。

事务协调器使用一个特殊的topic:transaction,来做事务提交日志。

事务控制器通过RPC调研,协调 broker 和 consumer coordinator 实现事务的两阶段提交。

每一个broker都会启动一个事务协调器,使用hash(TransactionalId)确定producer对应的事务协调器,使得整个集群的负载均衡。

3) broker

broker处理在事务协调器的commit/abort控制消息,把控制消息向正常消息一样写入topic(和正常消息交织在一起,用来确认事务提交的日志偏移),并向前推进消息提交偏移hw。

4) 组协调器

如果在事务过程中,提交了消费偏移,组协调器在offset log中写入事务消费偏移。当事务提交时,在offset log中写入事务offset确认消息。

5)consumer

consumer过滤未提交消息和事务控制消息,使这些消息对用户不可见。

有两种实现方式,

  • consumer缓存方式

设置isolation.level=read_uncommitted,此时topic的所有消息对consumer都可见。

consumer缓存这些消息,直到收到事务控制消息。若事务commit,则对外发布这些消息;若事务abort,则丢弃这些消息。

  • broker过滤方式

设置isolation.level=read_committed,此时topic中未提交的消息对consumer不可见,只有在事务结束后,消息才对consumer可见。

broker给consumer的BatchRecord消息中,会包含以列表,指明哪些是"abort"事务,consumer丢弃abort事务的消息即可。

事务消息处理流程如图1所示,

图1 事务消息业务流程

图1 事务消息业务流程

流程说明:

1. 查找事务协调器 -- FindCoordinatorRequest

事务协调器是分配pid和管理事务的核心,produer首先对任何一个broker发送FindCoordinatorRequest,发现自己的事务协调器。

2. 申请pid -- InitPidRequest

紧接着,producer向事务协调器发送InitPidRequest,申请生成pid。

2a.当指定了transactional.id时,事务协调器为producer分区pid,并更新epoch,把(tid,pid)的映射关系写入事务日志。 同时清理tid任何未完成的事务,丢弃未提交的消息。

3. 启动事务

启动事务是producer的本地操作,促使producer更新内部状态,不会和事务协调器发生关系。

事务协调器自动启动事务,始终处在一个接一个的事务处理状态机中。

4. consume-transform-produce 事务循环
4.1. 注册partition -- AddPartitionsToTxnRequest

对于每一个要在事务中写消息的topic分区,producer应当在第一次发消息前,向事务处理器注册分区。

4.1a.事务处理器把事务关联的分区写入事务日志。

在提交或终止事务时,事务协调器需要这些信息,控制事务涉及的所有分区leader完成事务提交或终止。

4.2. 写消息 -- ProduceRequest

4.2a. producer向分区leader写消息,消息中包含tid,pid,epoch和seq。

4.3. 提交消费偏移 -- AddOffsetCommitsToTxnRequest

4.3a. producer向事务协调器发送消费偏移,事务协调器在事务日志中记录偏移信息,并把组协调器返回给producer。

4.4. 提交消费偏移 -- TxnOffsetCommitRequest

4.4a. producer向组协调器发送TxnOffsetCommitRequest,组协调器把偏移信息写入偏移日志。但是,要一直等到事务提交后,这个偏移才生效,对外部可见。

5. 提交或终止事务
5.1. EndTxnRequest

收到提交或终止事务的请求时,事务处理器执行下面的操作:

1. 在事务日志中写入PREPARE_COMMIT或PREPARE_ABORT消息(5.1a)。

2. 通过WriteTxnMarkerRequest向事务中的所有broker发事务控制消息(5.2)。

3. 在事务之日中写入COMMITTED或ABORTED消息(5.3)。

5.2. WriteTxnMarkerRequest

这个消息由事务处理器发给事务中所涉及分区的leader。

当收到这个消息后,broker会在分区log中写入一个COMMIT或ABORT控制消息。同时,也会更新该分区的事务提交偏移hw。

如果事务中有提交消费偏移, broker也会把控制消息写入 __consumer-offsets log,并通知组协调器使事务中提交的消费偏移生效。

5.3. 写最终的commit或abort消息

当所有的commit或abort消息写入数据日志,事务协调器在事务日志中写入事务日志,标志这事务结束。

至此,本事务的所有状态信息都可以被删除,可以开始一个新的事务。

在实现上,还有很多细节,比如,事务协调器会启动定时器,用来检测并终止开始后长时间不活动的事务,具体请参考下面列出的kafka社区技术文档。

【总结】:

我们要认识到,虽然kafka事务消息提供了多个消息原子写的保证,但它不保证原子读。

例如,

1)事务向topic_a和topic_b两个分区写入消息,在事务提交后的某个时刻,topic_a的全部副本失效。这时topic_b中的消息可以正常消费,但topic_a中的消息就丢失了。

2)假如consumer只消费了topic_a,没有消费topic_b,这样也不能读到完整的事务消息。

3)典型的kafka stream应用从多个topic消费,然后向一个或多个topic写。在一次故障后,kafka stream应用重新开始处理流数据,由于从多个topic读到的数据之间不存在稳定的顺序(即便只有一个topic,从多个分区读到的数据之间也没有稳定的顺序),那么两次处理输出的结果就可能会不一样。

也就是说,虽然kafka log持久化了数据,也可以通过指定offset多次消费数据,但由于分区数据之间的无序性,导致每次处理输出的结果都是不同的。这使得kafka stream不能像hadoop批处理任务一样,可以随时重新执行,保证每次执行的结果相同。除非我们只从一个topic分区读数据。

【参考】:

[0] https://cwiki.apache.org/confluence/display/KAFKA/Idempotent+Producer
[1] https://cwiki.apache.org/confluence/display/KAFKA/KIP-98+-+Exactly+Once+Delivery+and+Transactional+Messaging#KIP-98-ExactlyOnceDeliveryandTransactionalMessaging-4.1AddPartitionsToTxnRequest
[2]https://docs.google.com/document/d/11Jqy_GjUGtdXJK94XGsEIK7CP1SnQGdp2eF0wSw9ra8/edit#
[3]https://cwiki.apache.org/confluence/display/KAFKA/Transactional+Messaging+in+Kafka
[4]https://www.confluent.io/blog/exactly-once-semantics-are-possible-heres-how-apache-kafka-does-it/
[5]https://www.confluent.io/blog/transactions-apache-kafka/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343