Kafka学习笔记 --- 如何实现Kafka消息的Exactly-Once

ぐ巨炮叔叔 提交于 2019-11-27 05:58:57

对于这个问题我们先来看一下一个笑话:

 

 

 

可以这样的实现

 

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。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!