前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Kafka设计-恰好一次和事务消息

Kafka设计-恰好一次和事务消息

作者头像
王知无-import_bigdata
发布2020-03-18 17:03:33
2.1K0
发布2020-03-18 17:03:33
举报

1.幂等消息

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

实现逻辑很简单:

  • 区分producer会话

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

  • 消息检测

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

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

代码语言:javascript
复制
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. 查找事务协调器 -- 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事务消息提供了多个消息原子写的保证,但它不保证原子读。

例如,

代码语言:javascript
复制
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/

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-03-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 大数据技术与架构 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.幂等消息
  • 2.事务消息
    • 【参考】:
    相关产品与服务
    负载均衡
    负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档