文章目录
深入客户端
1. 分区分配策略
设置消费者与订阅主题之间的分区分配策略。
1.1 RangeAssignor分配策略
RangeAssignor 分配策略的原理是**按照消费者总数和分区总数进行整除运算来获得一个跨度,然后将分区按照跨度进行平均分配,以保证分区尽可能均匀地分配给所有的消费者。**
==通俗地讲: m = 分区数 / 消费者 n = 分区数 % 消费者 ==
==前 n 个消费者 分配 m + 1个分区
后 面的消费者 分配 m 个分区 ==
这样分配的缺点?
但是这样不配并不均匀,如果只有3个分区,2个消费者,那么第一个消费者会一直分配2个分区,这样的现象如果扩大,使得部分消费者过载。
1.2 RoundRobinAssignor分配策略
分配策略的原理是将消费组内所有消费者及消费者订阅的所有主题的分区按照字典序排序,然后通过轮询方式逐个将分区依次分配给每个消费者。
1.3 StickyAssignor分配策略
(1)分区的分配要尽可能均匀。
(2)分区的分配尽可能与上次分配的保持相同。
1.4 也可以进行自定义分配策略
可以通过自定义分区分配策略来打破Kafka中“一个分区只能被同一个消费组内的一个消费者消费”的禁忌。
有一个严重的问题—默认的消费位移的提交会失效。所有的消费者都会提交它自身的消费位移到__consumer_offsets中,后提交的消费位移会覆盖前面提交的消费位移。
应用价值不大。
2. 消费者协调器和组协调器
如果消费者客户端中配置了两个分配策略,那么以哪个为准呢?
如果有多个消费者,彼此所配置的分配策略并不完全相同,那么以哪个为准?
多个消费者之间的分区分配是需要协同的,那么这个协同的过程又是怎样的呢?
这一切都是交由消费者协调器(ConsumerCoordinator)和组协调器(GroupCoordinator)来完成的
2.1 旧版消费者客户端存在的问题
==旧版的kafka并没有组协调器 和 消费协调器 ==,是通过zookeeper 的监听器(Watcher)来实现的。
每个消费组(<group>)在ZooKeeper中都维护了一个/consumers/<group>/ids路径,在此路径下使用临时节点
每个消费者在启动时都会在**/consumers/<group>/ids和/brokers/ids** 路径上注册一个监听器。
当/consumers/<group>/ids路径下的子节点发生变化时,表示消费组中的消费者发生了变化;
当/brokers/ids路径下的子节点发生变化时,表示broker出现了增减。
这样通过ZooKeeper所提供的Watcher,每个消费者就可以监听消费组和Kafka集群的状态了。
这样带来的问题?
(1)羊群效应(Herd Effect):所谓的羊群效应是指ZooKeeper中一个被监听的节点变化,大量的 Watcher 通知被发送到客户端,导致在通知期间的其他操作延迟,也有可能发生类似死锁的情况。
(2)脑裂问题(Split Brain):消费者进行再均衡操作时每个消费者都与ZooKeeper进行通信以判断消费者或broker变化的情况,由于ZooKeeper本身的特性,可能导致在同一时刻各个消费者获取的状态不一致,这样会导致异常问题发生。
2.2 新版客户端再均衡原理
全部消费组分成多个子集,每个消费组的子集在服务端对应一个GroupCoordinator对其进行管理,GroupCoordinator是Kafka服务端中用于管理消费组的组件。
ConsumerCoordinator与GroupCoordinator之间最重要的职责就是负责执行消费者再均衡的操作,包括前面提及的分区分配的工作也是在再均衡期间完成的。
什么情况会触发再均衡(Reblance)?
- 有新的消费者加入消费组。
- 有消费者宕机下线。消费者并不一定需要真正下线,例如遇到长时间的 GC、网络延迟导致消费者长时间未向GroupCoordinator发送心跳等情况时,GroupCoordinator会认为消费者已经下线
- 有消费者主动退出消费组
- 消费组所对应的GroupCoorinator节点发生了变更
- 消费组内所订阅的任一主题或者主题的分区数量发生变化。
3. __consumer_offsets剖析
位移提交的内容最终会保存到Kafka的内部主题__consumer_offsets中。
一般情况下,当集群中第一次有消费者消费消息时会自动创建主题__consumer_offsets
==副本因子==还受offsets.topic.replication.factor参数的约束,这个参数的默认值为3
==分区数==可以通过offsets.topic.num.partitions参数设置,默认为50
__consumer_offsets的日志保留机制?
按照broker 端的配置 offsets.retention.minutes 来确定保留时长。默认值为10080,即**7天**,超过这个时间后消费位移的信息就会被删除(使用墓碑消息和日志压缩策略)。
如果如果设置不当,导致消费位移丢失,那么就需要从客户端设置的 auto.offset.reset来决定开始消费的位置 earlest latest
如果有若干消费者消费了某个主题中的消息,并且也提交了相应的消费位移,那么在删除这个主题之后会一并将这些消费位移信息删除。
4. 事务
4.1 消息传输保障
一般而言,消息中间件的消息传输保障有3个层级。
- at most once:至多一次。消息可能会丢失,但绝对不会重复传输。
- at least once:最少一次。消息绝不会丢失,但可能会重复传输。
- exactly once:恰好一次。每条消息肯定会被传输一次且仅传输一次。
生产者和消费者都存在最多一次 和 至少一次 的情况。
kafka0.11开始引入了幂等和事务这两个特性,以此来实现EOS
4.2 幂等性
幂等,简单地说就是对接口的多次调用所产生的结果和调用一次是一致的。
生产者在进行重试的时候有可能会重复写入消息,而使用Kafka的幂等性功能之后就可以避免这种情况。
如何开启幂等?
显式地将生产者客户端参数enable.idempotence设置为true
如果用户还显式地指定了 acks 参数,那么还需要保证这个参数的值为-1(all),如果不为-1(默认为1)
生产者如何保证幂等?
broker端会在内存中为每一对<PID,分区>维护一个序列号。对于收到的每一条消息,只有当它的序列号的值(SN_new)比broker端中维护的对应的序列号的值(SN_old)大1(即SN_new=SN_old+1)时,broker才会接收它。
如果SN_new<SN_old+1,那么说明消息被重复写入,broker可以直接将其丢弃。
如果SN_new>SN_old+1,那么说明中间有数据尚未写入,出现了乱序,暗示可能有消息丢失,对应的生产者会抛出OutOfOrderSequenceException。
引入序列号来实现幂等也只是针对每一对<PID,分区>而言的,也就是说,Kafka的幂等只能保证单个生产者会话(session)中单分区的幂等。
上面示例中发送了两条相同的消息,不过这仅仅是指消息内容相同,但对Kafka 而言是两条不同的消息,因为会为这两条消息分配不同的序列号。Kafka 并不会保证消息内容的幂等。
4.3 事务
幂等性并不能跨多个分区运作,而事务可以弥补这个缺陷。
事务可以保证对多个分区写入操作的原子性。操作的原子性是指多个操作要么全部成功,要么全部失败,不存在部分成功、部分失败的可能。