对于这个问题我们先来看一下一个笑话:
可以这样的实现
kafka设计思想:
Kafka 0.11.0 版本之前并不能保证Exactly-once的语义,只能保证 at-least-once or at-most-once。实际运用中我们并不希望数据丢失,当网络出现问题的时候,我们都会选择重试,所以一般会保证at-least-once。
下面从三个点来讲解:
-
idempotent Producer 设计
-
Transactional Messaging in Kafka 设计
-
Message format 改变
1.Idempotent Producer
我们要保证 Producer 发送数据幂等性,可以给每条数据分配一个UUID,Server 端存储所有的id。接收到数据的时候进行检测,如果重复就拒绝。这样的设计有一个问题就是算法复杂度的问题,我们需要匹配所有的消息的UUID。所以我们需要一个对空间资源要求低且查询速度快的设计思路。
我们可以给 Producer 分配一个PID,message分配一个自增的sequence number, sequence number如果是一个全局的id,分配到不同的partition,出现问题的时候回滚会比较麻烦,所以我们可以针对partition的message分配一个PID,message分配一个自增的sequence number,sequence number如果是一个全局的Id,分配到不同的partition,出现问题的时候回滚会比较麻烦,所以可以针对partition的message分配sequence number。server的某个partition只接受比当前sequence number + 1的数据。这种设计就是细粒度到PID-topic-partition-message维度。
2.Idempotent Producer Server端设计
根据上面的思路,Server端需要实现一个分配PID的过程。设计一个pid array, pid分配的时候支持自增保证唯一性。如果我们把这个设计放在Broker端来做,一个Broker分配一个PID之后宕机了,备份节点顶替Leader节点,因为Leader节点宕机,副本节点并没有有同步完成PID数据,PID可能被分配多次。所以需要采取另外一种策略,使用Zookeeper,实现一个ID生成器,Server可以每次拿100个PID进行分配,用完了再取100个PID进行分配。那么Leader节点宕机,副本节点顶替Leader节点之后,可以重新拿100个PID资源进行分配。
3.Idempotent Producer client 端设计
Producer和Consumer请求需要支持设置PID sequence number这些字段。
4.Transactional Messaging in Kafka设计
什么是事务性消息
比如Producer基于事务发送消息,这个事务内的消息可以进行提交或者中断,并保证一下特点:
-
Atomicity:未提交的消息不能暴露给Consumer。
-
Durabilitys:Broker不能丢失已提交的消息。
-
Ordering: consumer消息得到的事务消息应该和消息提交的顺序保持一致。
-
Interleaving:每个Partition可以接受事务消息,也可以接受非事务消息。
-
在事务消息中,应该保证消息不重复。
Kafka中如何支持了READ_COMMITTED隔离级别
上图开启两个事务,X2事务先提交,X1事务后提交。所以Consumer处理的顺序应该是先X2的数据,后X1的数据。这就是我们想要创建的事务消息。
kafka通过下面的设计做到事务上面的这一点:
-
Kafka在Consumer端设置一个Buffer缓存存储未提交的数据。Pending等到接受到事务的commit log。再进行消费。这样就可以做到READ_COMMITED的隔离级别。
-
Kafka增加了一个内部的协调器-transaction coordinator,Producer commit事务会把commit log提交给协调器,commit log会存储到内部的_transaction_control topic中。
Consumer Pending事务数据直接消费transaction_control topic 得到事务提交数据。
同时Kafka要支持READ_UNCOMMITTED 隔离级别,Consumer不pending事务数据就可以了。
5.Message Format的改变
Idempotent Producer 我们要支持给 Producer 分配PID, 消息格式需要保存 sequence number。
Transactional Messaging 我们要新增一个Transactional coordinator协调器,Broker 新增一个_transaction_control topic 接受事务消息。
所以 Message 要保存PID,sequence number, transactional Id。
新版本的 MessageSet 增加了PID字段, sequence number 通过 MessageSet 中的 FirstSequence 字段和 Message中的OffsetDelta 字段 “firstsequence+offsetDelta” 公式 计算得到。
事务Id存储在 MessageSet 的 Attributes 字段。如下图:
新的数据格式采用了压缩的原理,将公共字段存储到 MessageSet中,将尽量少的字段存储在Message中,做到了数据压缩。随着 Batch的增大,Message 一条数据头部增长 2 bytes(for the message size), 1 byte(for attributes), 2 bytes for(timestamp delta), 1 byte(for offset delta), and 1 byte(for key size)。
新增Control 类型的消息
因为事务提交需要一种commit message,这里使用Message的Attributes字段中一个字节标识是否是control message。如下图:
6.总结:
根据上面的描述我们总结如下:
-
Idempotent新增了PID-Topic-Partition-SequenceNumber,用Zookeeper做了一个Id生成器支持PID的发送。
-
Transaction Message新增了一个Transaction coordintor协调器,并且新增了内部topic存储事务提交的消息。
-
Consumer 使用本地Buffer pending 往未提交事务的消息直到接受到相应的control Message。
来源:https://blog.csdn.net/u012965373/article/details/99479192