Kafka事务
事务就是保住消息消费的原子性和稳定性。
消息语义
at most once: 最多一次,发了就不管了,爱去哪里去哪里
at least once: 至少一次,一定要你给我回复了,我才停止发送
exactly once: 恰好一次,每条消息被精确的发送
以上前两种都是可以使用生产者ACK机制来实现的,但是精准的一次需要幂等性来协助。
注意幂等性不等于就实现了精确的一次,实际业务中还需要考虑消费者端的问题:比如消息写入数据库失败了之类的问题。
幂等性
-
每个生产者会被分配一个ProducerId(PID),SeqNumber
-
然后生产者端和Broker端都有<PID,PartitionID> SeqNumber 的映射关系
-
生产者每发送一条消息后就将对应的分区序列号加一
-
broker端会比较序列号,如果new Sq < old Sq+1,抛弃这条数据。说明它已经是过期的了
如果new Sq > old sq +1,说明有消息丢失了。对生产者抛出异常
怎么保证PID相同的生产者干扰数据
在分配PID时,会分配epoch,新的生产者就会加1,如果出现了两个同样的生产者PID一样,取epoch最大的那个。
幂等性只能保证单个生产者会话中的单个分区数据有序
事务
事务是建立在幂等性上,需要开启幂等性。
它可以保证在不同分区上事务,从生产->消费的原子性。这里不是绝对的,由于以下原因:
- 消费者可以从任意的offset去消费
- 事务内消息如果存在两个Segment上,突然有一个被清理了
- 消费者在组协调器分配分区的时候,它没有分配到事务内的所有的分区
总结原理:
通过一个TransactionId与协助的主题记录事务的过程
然后消费者端缓存消息,根据最后命令是否提交,来决定是否给我用户写的业务程序代码。
具体步骤
-
生产者端用户自己配置一个TransactionId
-
然后向Broker发送查找事务协调器的请求,然后找到对应host,并且分配PID
-
向事务协调器发送InitProducerId的请求,然后协调器将PID保存在__transcation_state的主题中
-
生产者发送AddPartitionToTxn请求,告诉事务协调器我要将消息发送到那个分区,协调器将<TrasactionId,TopicAndPartition>保存到__transcation_state。
-
生产者开始正式向用户自定义的主题发送消息,这一步就用到上面幂等性的原理检查数据
-
生产者发送AddOffestsToTxnRequest携带<TopicPartition,OffsetAndMetadata>给协调器,
协调器通过groupId推导出___consumter_offsets中的分区,并保存下来
-
生产者再次发送TxnOffsetCommit给组协调者,让它更新本次事务的位移及其他信息。
注意:高能警告这一步的协调者是组协调者,不是事务协调者
8.发送终止的事务请求EndTxn给事务协调者,然后事务协调者向__transaction_state主题,写入准备提交或者放弃的标识:Prepare_commit或者Prepare_abort的消息
-
然后事务协调者通过WriteTxnMarker,向用户自定义的主题和___consumer_offset写入结束标记
complete_commit或者complete_abort的标记消息,这种标记消息对于实际的处理消息的程序或者说代码是不见的
-
消费者通过最后标识判断缓存的消息是否下发给后面的程序,并清理____transaction_state的数据
一些思考
关于上面几步中6,7步对位移的操作,不太理解。
它表达的是在流式程序中,先启动消费者,应用程序转换消息,通过生产者发送消息,生产者再来提交偏移量或者提交事务的情况。其实如果不是在这种
consumer-transform-producer的模式中是可以不调用的,如果它是消费链条中第一步。