doc: 阅读kafka exactly-once文档,并做出补充

This commit is contained in:
asahi
2025-11-13 19:23:19 +08:00
parent c6e216db9a
commit f07db479b0

View File

@@ -38,6 +38,7 @@
- [消费者](#消费者)
- [push vs pull](#push-vs-pull)
- [consumer position](#consumer-position)
- [writing to an external system](#writing-to-an-external-system)
- [静态成员](#静态成员)
- [消息传递语义](#消息传递语义)
- [复制](#复制)
@@ -347,8 +348,29 @@ Kafka消费者会向其消费分区的leader broker发送fetch请求。消费者
#### push vs pull
在Kafka的实现中和大多数消息系统一样data从生产者push到broker消费者从broker中pull data。
#### consumer position
其他消息系统消费者通常使用ack来告知broker该消息已经被正确处理但是这样会存在如下问题如果被consumer拉取的消息被处理但是在发送ack之前消费者失败那么该消息会被重复消费。
相比于通过ack来保证消息被消费者正确处理kafka通过其他方式来对其进行处理。kafka topic由一系列分区组成在一个时间点每个分区都只会由一个consumer group中的一个消费者实例消费。每个消费者实例只需要维护一个integer作为ack确认的等价物维护position的开销相当小。
大多数消息系统会在broker端通过metadata来追踪哪些消息已经被消费当消息被实际传送给consumer或被consumer所ack时会记录消息此时的状态。使用该方式会存在如下问题
- 如果在消息被传递给consumer后立马将该消息标记为已消费
- 若consumer接收到消息后如果处理该消息失败那么该消息会丢失
- 如果在消息被consumer成功处理且consumer向broker发送ack后broker才将消息标记为已消费
- 如果consumer成功处理该消息但是向broker发送ack失败那么该消息会被重复传递并消费这会导致消息的重复消费
- broker需要为消息维护多个状态已发送未消费/已消费/未发送),这将会带来性能问题
相比于通过ack来保证消息被消费者正确处理kafka通过其他方式来对其进行处理。kafka topic由一系列分区组成在一个时间点每个分区都只会由一个consumer group中的一个消费者实例消费。每个消费者实例只需要维护一个integer作为ack确认的等价物维护position的开销相当小。
并且通过offset方案还支持通过rewind对旧消息进行重复消费在一些场景下该特性十分有用。
##### writing to an external system
当消息处理的逻辑中存在`将output向外部系统写入时`需要协调consumer position和`向外部系统写入output`两个操作。通常,在实现该协调过程时,会引入两阶段提交,保证`consumer position的存储``consumer output`的存储是一致、原子的,要么都发生,要么都不发生。
但是,通常情况下,会`在相同的位置存储offset和output`,这样可以无需引入两阶段提交,尤其在外部系统并不支持两阶段提交协议的情况下。
> 例如在消息消费时如果要将消息消费结果写入到mysql数据库中时也可以同时在数据库中记录`(topic, partition, consumer group, last consumed offset)`的信息此时可以通过数据库事务来保证offset的更新和消息结果的写入同时成功/失败。
>
> 并且在consumer实例启动、rebalance分配到分区时都会从数据库中读取offset这样能够保证消息的exactly-once消费。
#### 静态成员
静态成员用于提升基于group rebalance协议应用的性能。rebalance协议依赖于组协调器组协调器用于为组成员分配实体id。组协调器产生的实体id是短暂的并且当成员重启并重新加入到组之后实体id会发生变化。对于基于consumer的app上述“动态成员”可能会造成在应用周期性重新启动时大量任务会被新分配给其他消费者实例。
由于上述缺点Kafka group管理协议允许组成员提供持久的实体id基于这些id组成员身份保持不变故而rebalance不会被触发。