分区分配策略
客户端可以自定义分区分配策略, 当然也需要考虑分区消费之后的offset提交, 是否有冲突。
消费者协调器和组协调器
a. 消费者的不同分区策略, 消费者之间的负载均衡(新消费者加入或者存量消费者退出), 需要broker做必要的协调。
b. Kafka按照消费组管理消费者, 鉴于offset提交最终都是在某个broker节点上完成。该broker扮演GroupCoordinator角色, 具体的选择则是通过hash快速定位。
c. client端存在一个ClientCoordinator与目标的GroupCoordinator进行通信实现最终协调;
d. 具体过程如下
ClientCoordinator Broker(Min Load) Broker(GroupCoordinator) Broker(To consumer) 1. Find_Coordinator request Find_Coordinator response 2. Join_Group request 3.1 calculate brokerId 3.2 Elect leader consumer 3.3 Elect partition strategy . Join_Group response, isLeader 4. Sync_Group Request Sync_Group Response 5. Poll offset/message, HeartBeat response offset/heartbeat/message ClientCoordinator Broker(Min Load) Broker(GroupCoordinator) Broker(To consumer)
关于__consumer_offset
__consumer_offset是一个特殊的topic, 用于存储每个topic中partition中client提交的offset。其中的数据保留时间通过offset.retention.minutes配置。如果consumer消费消息的间隔超过了配置时间, 则offset会丢失, consumer再次获取offset时会因为没有存量的offset而自动重置(auto.offset.reset)。该topic下的消息清理采用压缩策略(仅保留最新消息)。Kafka中会有定时清理任务清理过期的消费位移。
消息发送QoS
- at-least-once, 至少一次, 消息不会丢失, 但消息会重复;
- at-most-once, 至多一次, 消息不会重复, 但可能会丢失;
- exact-once, 恰好一次, 消息肯定被传输且只传输一次;(如果开发即时消息系统, 那么这个语义就是我们的目标)
默认情况下, Kafka producer在发送时, 如果消息发送失败会自动进行重试, 重试过程可能会导致消息重复。而一旦发送成功, Kafka通过多副本机制保证消息一定会被保存。因此从consumer角度观察, producer发送的结果, 其QoS是at-least-once。如果需要exact-once, 则需要启用Kafka的幂等特性。
幂等
-
配置参数
enable.idompotence=true
retrics > 0
max.in.flight.requests.per.connection <=5
ack=-1
-
实现细节
首先幂等是partition级别, broker端自动为producer分配一个PID, 并维护PID->分区(序列号 lastSeq) 的状态。当producer发送消息时, 必须携带该序列号newSeq。broker端收到消息时做校验:
a. newSeq = lastSeq+1, broker接收;
b. newSeq > lastSeq+1, 中间存在消息丢失, 抛出OutOfOrderException;
c. newSeq < lastSeq+1, 消息存在重复, 直接丢弃即可.
事务消息
如果要实现跨parition的exact-once语义, 则需要基于事务消息。一般来说事务有ACID的特性, 但这个是数据库事务的通用场景。Kafka下消息需要考虑生产和消费, 这里的事务消息更多是生产端的事务消息。消费端可能会因为某些原因无法以事务的形式消费。比如:
- 对于采用日志压缩策略的主题而言, 事务中的消息被清理(对相同key的消息后写入的消息会覆盖之前写入的消息);
- 事务涉及的分区多个日志段, 如果老的日志分段被删除, 对应的消息也会消失;
- 消费者通过seek消费消息, 造成消息遗漏;
- 消费者在消费时没有消费到事务涉及的所有分区, 因此不能读取事务中的所有消息;
总的来说, 事务保证了生产者可以以事务的方式实现消息发送的exact
-once语义, 但消息清理和消费并未引入事务约束。
实现原理
- 开启幂等;
- 设置事务ID, transactional.id;
- 生产者通过事务ID得到PID和producer epoch, 进而实现跨生产者会话的消息发送和事务恢复。前者保证相同transactionId的生产者仅有1个可以有效发送消息, 后者保证如果事务消息发送后宕机新恢复出来的生产者可以继续提交或者终止事务。其中包含2个方面, 生产者的唯一性, 其关联的在途事务的可见性和可操作性。
- broker端为支持事务消息引入了事务协调器, 与组协调器类似, 用于处理事务的提交和终止。
- 具体交互流程如下
事务存储
- 日志存储按Topic, Partition和LogSegment层级存储, 事务消息也不例外;
- 与普通消息的区别是, 事务消息更多适用于发送一组消息的场景, 具体到LogSegment就是有一组连续的消息, 因此Kafka引入了ControlBatch消息来标志消息结束。
- 事务消息的开始在哪里呢? 严格来说, producer跨分区发送成功后, consumer是无法恢复出原有的顺序, 在分区级别仅可以做到与某个事务关联的一组消息(通过消息的属性标志是否为事务消息), 结束通过ControlBatch标志一组消息结束。
小结
本文讨论了Kafka发送消息的三种语义at-least-once, at-most-once, exact-once,并针对exact-once的单分区实现(幂等控制)和跨分区实现(事务消息)做简要介绍, 希望能帮助你梳理出Kafka broker端对消息发送QoS实现的基本脉络, 为进一步学习打基础。