使用Apache Kafka 消费者组时,有一个为消费者分配对应分区partition的过程,我们可以使用“自动”subscribe和“手动”assign的方式。
同时进行“自动”和“手动”的分区分配是会互相影响的,有时会把事情搞糟。正确的使用,首先要了解这两种方式的场景。
一、消费者组的使用场景
Kafka里的消费者组有两个使用的场景:
- “队列模式”:在同一组的消费者共同消费一个主题的所有消息,而且确保一条消息只被一个消费者处理。一个主题的所有的分区会和一个消费组的所有消费者做关联:每个消费者和一到多个分区做关联,接收它们的消息。反向说,一个分区只会与一个消费者关联,它的消息不会被其它的消费者接收。
最开始只有一个消费者时,所有的分区都分配给了它。当消息的规模增加时,我们就需要扩展消费者的数量,水平扩展处理能力,一直可以达到每个消费者只关联一个分区。大于分区数的消费者是会处在空闲状态,因为没有分配任何的分区。 - “发布/订阅模式” 创建不同的消费者组意味一个主题的消息会发送给所有订阅它的消费者组,然后消费者组依照前面共同协作的场景进行分配。这往往是因为我们有不同的应用需求,比如一批交易数据,资金系统、ERP系统会消费它而风险监控也需要同时消费它。这就实现了数据的透明异步共用。
在两个场景中,消费者组有个重要的功能:rebalancing。当一个新的消费者加入一个组,如果还有有效的分区(消费者数<=主题分区数),会开始一个重新均衡分配的操作,会将一个已关联的分区(它的原消费者仍保有至少一个分区)重新分配给新加入的消费者。同样的,当一个消费者因为各种原因离开这个组,它的所有分区会被分配给剩下的消费者。
二、“自动” OR “手动”
前面所说的自动分配是指在 KafkaConsumer API中的subscribe()方法。这个方法强制要求你为消费者设置一个消费者组,group.id参数不能为空。而你不需要处理分区的分配问题。
而对应subscribe()方法,你可以采用手动的方式,指定消费者读取哪个主题分区,则:assign() 方法。当你需要精确地控制消息处理的负载,也能确定哪个分区有哪些消息时,这种手动的方式会很有用。但这时Kafka也无法提供rebalancing的功能了。而且在使用手动的方式时,你可以不指定消费者组,group.id为空。
两种方式都各有适用场景,但同时不建议同时使用两种方式,这会带来风险。假设一个消费者组G1,组内只有一个消费者C1,订阅subscribe了一个具有两个分区P1、P2的主题T1。这时在G1新增一个消费者C2,用assign的方式关联P1和P2。视乎一切都可行,但其实是糟糕的情况。本质上,使用场景被混淆了,你无法确定G1是在共同协助还是在进行发布/订阅。实际使用中,offset的提交格式是这样的:
key = [group, topic, partition]
value = offset
注意Key中并未区分消费者,C1和C2会对同一个key会脏写。代表着C1或C2奔溃重启时可能会拿到对方重写覆盖的offset,消息也会有丢失。
三、总结
最好是使用subscribe()方法,让分区自动分配。毕竟Kafka的消费者组机制已经很优秀,为我们节省了很多功夫。哪怕你需要采用assign()指定的方式,也应该设置好对应的消费者组。尽量别混合使用。