kafka 位移主题 __consumer_offsets
kafka 位移主题 __consumer_offsets
位移主题 诞生背景
- 老版本的Kafka会把位移信息保存在Zookeeper中,当Consumer重启后,自动从Zk中读取位移信息。这种设计使Kafka Broker不需要保存位移数据,可减少Broker端需要持有的状态空间,有利于实现高伸缩性。
- Zookeeper不适用于高频的写操作,这令zk集群性能严重下降,在新版本中将消费者的位移数据作为一条条普通的Kafka消息,提交至内部主题(_consumer_offsets)中保存。实现高持久性和高频写操作。
这些__consumer_offsets-xxx
就是Kafka的位移主题,Kafka将 Consumer 的位移数据作为一条条普通的 Kafka 消息,提交到 __consumer_offsets 中。
位移offset:
针对 Consumer Group,Kafka需要记录当前分区当前消费组消费的位置,这个位置我们称之为位移(Offset)。Kafka消费位移是记录下一条要消费的消息的位移,而不是目前最新消费消息的位移。
Consumer 需要向 Kafka 汇报自己的位移数据,这个汇报过程被称为提交位移(Committing Offsets)。
因为 Consumer 能够同时消费多个分区的数据,所以位移的提交实际上是在分区粒度上进行的,即 Consumer 需要为分配给它的每个分区提交各自的位移数据。
如果提交了位移x,那么认为所有小于位移x的消息都已经被消费。位移提交的语义保障是由消费者来负责的,Kafka 只会“无脑”地接受消费者提交的位移。对位移提交的管理直接影响了Consumer 所能提供的消息语义保障。
位移主题的特点:
- 位移主题是一个普通主题,同样可以被手动创建,修改,删除。
- 位移主题的消息格式是kafka定义的,不可以被手动修改,若修改格式不正确,kafka将会崩溃。
位移主题Key:
位移主题的 Key 中应该保存 3 部分内容:<Group ID,主题名,分区号 >
位移主题Value:
位移主题的Value也就是消息体有三种:
- 位移信息与一些元数据信息(比如时间戳等)
- 用于保存 Consumer Group 信息的消息。
- 用于删除 Group 过期位移甚至是删除 Group 的消息。它有个专属的名字:tombstone 消息,即墓碑消息,也称 delete mark。一旦某个 Consumer Group 下的所有 Consumer 实例都停止了,而且它们的位移数据都已被删除时,Kafka 会向位移主题的对应分区写入 tombstone 消息,表明要彻底删除这个 Group 的信息。
位移主题的创建
- 当Kafka集群中的第一个Consumer程序启动时,Kafka会自动创建位移主题。也可以手动创建
- 分区数依赖于Broker端的offsets.topic.num.partitions的取值,默认为50 ;
- 副本数依赖于Broker端的offsets.topic.replication.factor的取值,默认为3;
自动提交位移&手动提交位移
Consumer 端有个参数叫 enable.auto.commit,如果值是 true,则 Consumer 在后台默默地为你定期提交位移,提交间隔由一个专属的参数 auto.commit.interval.ms 来控制.
如果你选择的是自动提交位移,那么就可能存在一个问题:只要 Consumer 一直启动着,它就会无限期地向位移主题写入消息。
举个例子:
假设 Consumer 当前消费到了某个主题的最新一条消息,位移是 100,之后该主题没有任何新消息产生,故 Consumer 无消息可消费了,所以位移永远保持在 100。由于是自动提交位移,位移主题中会不停地写入位移 =100 的消息。显然 Kafka 只需要保留这类消息中的最新一条就可以了,之前的消息都是可以删除的。这就要求 Kafka 必须要有针对位移主题消息特点的消息删除策略,否则这种消息会越来越多,最终撑爆整个磁盘。
Kafka 是怎么删除位移主题中的过期消息的呢?答案就是 Compaction.
Kafka 使用 Compact 策略来删除位移主题中的过期消息,避免该主题无限期膨胀。
对于同一个 Key 的两条消息 M1 和 M2,如果 M1 的发送时间早于 M2,那么 M1 就是过期消息。
Kafka 提供了专门的后台线程定期地巡检待 Compact 的主题,看看是否存在满足条件的可删除数据。这个后台线程叫 Log Cleaner。很多实际生产环境中都出现过位移主题无限膨胀占用过多磁盘空间的问题,如果你的环境中也有这个问题,我建议你去检查一下 Log Cleaner 线程的状态,通常都是这个线程挂掉了导致的。