(3)Kafka生产者分区策略、ISR、ACK、一致性语义
1.生产者分区策略
当生产者发送消息到某个Topic(主题)时,消息需要被分配到Topic的某一个具体分区中。这个分配策略由Partition(分区)决定,有如下三种分区策略:
①指定分区(Partition):直接在发送消息时指定目标分区。
②指定关键词(Key):为消息设置一个Key。Kafka会对Key进行哈希计算,根据哈希值与Topic分区数取余再决定分配到哪个分区。
●作用:同一Key的消息总是被发送到同一个分区,这保证了同一业务实体消息的顺序性。
③轮询策略(Round-Robin)(默认):如果消息既没有指定分区也没有Key,生产者先随机生成一个整数(之后每次在这个整数上递增1),将这个整数和Topic的分区数量进行取余得到分区号(Round-Robin算法),以轮询的方式将消息均匀地分配到所有可用分区上。
●作用:实现负载均衡,让数据均匀分布。
1.1生产者分区策略图解

1.2消息压缩
消息压缩可以提升Kafka的性能,生产者在发送消息的时候将消息按照指定的压缩算法进行压缩,达到降低网络IO、优化Broker磁盘空间的目的,但这样也会有更多的CPU消耗,因此是个需要权衡的功能。默认配置是不压缩,但Kafka也提供了多种高效的压缩算法供用户选择,可以在生产者端进行配置。主要的压缩算法有:
①gzip:压缩率高,但占用较多的CPU资源,速度相对较慢。
②snappy: 由Google开发,压缩率和速度之间取得了很好的平衡。压缩速度较快,但压缩率不如gzip。是很多场景下的热门选择。
③lz4: 以极高的压缩和解压速度著称,CPU开销非常低。虽然压缩率可能稍逊于snappy,但其速度优势非常明显,是目前非常推荐和使用广泛的压缩算法。
④zstd: 由Facebook开发,提供了非常高的压缩率,同时解压速度也很快。是较新加入的算法,在需要极高压缩率的场景下表现优异。
⑤它们的性能大致对比如下(具体情况取决于数据本身):
●压缩率: zstd > gzip > lz4 ≈ snappy
●CPU效率/速度: lz4 > snappy > zstd > gzip
各压缩算法的性能对比请参考:https://www.cnblogs.com/huxi2b/p/10330607.html
2.副本同步(ISR)
ISR是保证Kafka数据一致性和高可用的核心机制。
●定义:ISR是指与Leader副本保持同步的副本集合(包括Leader自己)。
●组成:ISR = Leader + 所有与Leader副本保持同步的Follower副本
●同步标准:Follower副本需要周期性地从Leader副本拉取消息,并保持一定的同步差距(由replica.lag.time.max.ms参数控制)。如果Follower落后太多或长时间未发起抓取(fetch)请求,就会被踢出ISR。
●作用:当Leader发生故障时,只有ISR中的副本有资格被选举为新的Leader,这样就保证了数据不会丢失(至少有一个副本拥有全部最新数据)。
2.1副本同步ISR图解

3. ACK
生产者发送消息后,需要等待Broker的确认,这个确认机制就是ACK。
●acks=0:“发后即忘”。生产者发送消息后,完全不等待Broker的任何确认。吞吐量最高,但数据可能丢失。
●acks=1(默认值):生产者等待Leader副本成功将消息写入其本地Log后,即可收到成功确认。折中方案。如果Leader刚写入就挂掉,且Follower还未复制,则消息会丢失。
●acks=all(或 acks=-1):最安全。生产者等待Leader收到所有ISR中的副本都成功写入消息后的确认。保证只要至少一个ISR副本存活,消息就不会丢失。
3.1ACK机制图解

3.2故障处理

首先根据上图我们来明确两个关键概念:Log End Offset、High Watermark。
3.2.1LEO (Log End Offset)
●是什么:每个副本(Leader和Follower)独有的一个值,每个副本内最大的offset。
●例子:如果一个副本的日志有5条消息(offset 0-4),那么它的LEO就是5。
●作用:它标识了一个副本本地日志的“长度”。
3.2.2HW (High Watermark)
●是什么:所有中都已成功复制的消息的偏移量。消费者能见到的最大的offset,为ISR(In-Sync Replicas)副本集合内最小的LEO。
●例子:如果HW=3,那么offset 0, 1, 2的消息是对消费者可见和可消费的。offset 3及之后的消息(即使已经写入Leader)对消费者不可见。
●作用:它定义了消息的提交(Committed)边界,是保证数据一致性和故障恢复的关键。HW之前的消息被认为是“已提交的”,意味着即使Leader宕机,这些消息也不会丢失。
3.2.3副本同步流程
如上图所示:
| 副本类型 | 消息数量 | HW | LEO |
| Leader | 13 | 8 | 12 |
| Follower 1 | 9 | 8 | |
| Follower 2 | 11 | 10 |
为了保证消费的一致性,避免出现当消费者消费到offset 11时发生故障,假设follower 1成为新leader,新leader消费时没有offset为11的数据,因此对于消费者来说只能看到并消费HW之前的数据。在这种机制下,当消费者消费到offset 8时,碰巧Leader出现故障,此时无论是ISR内的哪个follower作为新的Leader都能保证消费端的一致性。
3.2.4副本同步故障处理

●Follower:follower副本发生故障后会被临时踢出ISR集合(假设follower 2发生故障),当该follower恢复后,它会先读取上次记录的HW(比如上述offset 8),并将本地log文件内高于该HW的部分截取掉(比如follower 2中offset 9、10截取掉),然后从HW的offset开始跟Leader进行重新同步,保证自己的数据和当前的Leader数据一致。当该follower的LEO大于等于(重新超过)该分区的当前HW,就可以重新加入ISR了。
●Leader:Leader发生故障之后,控制器会从ISR集合内选举一个新的Leader(假设follower 1成为新leader),然后为了保证多个副本之间的数据一致性,其余的Follower会先将各自的log文件内高于HW的部分截取掉(比如follower 2中offset 9、10截取掉,旧leader副本重新恢复为新follower后也会截取掉offset 9、10、11、12),然后从HW的offset开始向Leader进行重新同步,保证自己的数据和当前新的Leader的数据一致。
4.一致性语义
消息系统中的“一致性语义”定义了消息在生产者(Producer)、Broker和消费者(Consumer)之间传递的可靠性保证级别。
它主要回答一个问题:一条被发送的消息,最终会被消费多少次?
Kafka主要提供了三种级别的一致性语义:
●至少一次(At-least-once)
●至多一次(At-most-once)
●精确一次(Exactly-once)
这三种语义的实现,是生产者发送消息的可靠性和消费者处理消息的可靠性两者共同作用的结果。
4.1至少一次(At-least-once)
如图所示:

●含义:消息绝不会丢失,但可能会被重复消费。
●生产者端:生产者发送消息后,会等待Broker的确认(acknowledgement)。如果它配置了acks=all,并且一直收不到Broker的成功ACK,它会重试发送消息。这可能导致Broker实际上已经写入成功,但ACK在网络中丢失,生产者重试后造成消息重复。
●消费者端:消费者读取消息,处理业务逻辑(如写入数据库),然后才提交偏移量(Offset)。如果在提交偏移量之前消费者进程崩溃,那么新的消费者实例会从旧的偏移量开始消费,导致这条已经被处理过的消息再次被消费。
●适用场景:需要保证数据绝不丢失,但下游业务可以处理重复数据(例如,业务逻辑本身是幂等的,或者有主键去重机制)。
4.2至多一次(At-most-once)
如图所示:

●含义:消息可能会丢失,但绝不会被重复消费。
●生产者端:生产者发送消息后不等待Broker的ACK(例如配置acks=0),或者重试次数设置为0。消息发送出去就认为成功,如果实际上失败,消息就丢失了。
●消费者端:消费者在读取消息后,先提交偏移量,然后再处理业务逻辑。如果在处理业务逻辑时崩溃,由于偏移量已经更新,消息就不会再被消费,从而丢失。
●适用场景:对数据丢失不敏感,但追求极高的吞吐量,例如一些实时监控日志采集。
4.3精确一次(Exactly-once)
如图所示:

●含义:消息有且仅被处理一次。这是最理想也是最难保证的语义。
Kafka通过事务(Transaction)和幂等性(Idempotence)机制来实现精确一次。这需要生产者和消费者的配合。
●生产者幂等性:通过给每个消息附加一个唯一的PID(Producer ID)和序列号(Sequence Number),Broker会据此对消息去重,避免因生产者重试导致的消息重复。
●跨分区/主题的事务:生产者可以将一批消息的发送和消费者的偏移量提交放在一个原子事务中。要么全部成功,要么全部失败。
●消费者事务性读取:消费者可以配置isolation.level=read_committed,这样它只会读取已提交事务的消息。
●适用场景:对数据一致性要求极高的金融、交易等核心业务场景。
4.4总结与对比
| 语义 | 生产者行为 | 消费者行为 | 优点 | 缺点 | 适用场景 |
| 至少一次 | 等待ACK,会重试 | 先处理,后提交Offset | 数据不丢失 | 可能重复消费 | 可处理重复数据的业务 |
| 至多一次 | 不等待ACK,不重试 | 先提交Offset,后处理 | 吞吐量高,无重复 | 可能丢失数据 | 日志、metrics收集 |
| 精确一次 | 启用幂等性和事务 | 配置为读取已提交事务 | 最强一致性 | 性能开销最大 | 金融、交易等核心业务 |
参考文献:
Kafka官网https://kafka.apache.org/documentation/#gettingStarted
