Kafka消费者组(设计亮点、架构、位移)

195 阅读7分钟

Kafka服务端

kafka发布消息的客户端是生产者,订阅消息的客户端是消费者
kafka的服务端即为broker的服务进行构成

一个kafka集群会有多个Broker(Broker 负责接收和处理客户端发送过来的请求,以及对消息进行持久化),一般来说不同的broker会分散在不同机器上,这样保证其中某一台机器宕机,即使他在这台机器上的所有broker进程掉了,也不会影响对外提供服务,这其实是Kafka高可用手段之一

一个分区中会有多个副本,kafka的副本机制是保证一个leader,n-1个follower,只有leader能向外提供服务,follower的工作只有向leader请求消息同步自己

Kafka三层消息架构

  • 主题层:一个topic会分为多个分区,一个分区会有多个副本
  • 分区层:一个分区的多个副本中,只有一个充当leader角色,对外服务,其他N-1个follower不能对外服务
  • 消息层:分区中包含若干条消息,每个消息的位移从0开始

Kafka持久化数据

Kafka使用消息日志(Log)来保存数据,一个日志就是磁盘上一个只能追加写(Append-only)消息的物理文件(追加写的是一个个segment) 因为只能追加写入,故避免了缓慢的随机 I/O 操作,改为性能较好的顺序 I/O 写操作,这也是实现 Kafka 高吞吐量特性的一个重要手段

一直写肯定会耗尽磁盘空间,kafka要定期清楚日志文件,底层是kafka的一个日志分为多个日志段(Segment)文件,当写满一个日志段后,自动切分出一个新的segment,然后将老的日志封存起来,Kafka在后台会定时检查老的日志段是否能够被删除,从而实现磁盘回收。

Kafka消费者组

Consumer Group 是 Kafka 提供的可扩展且具有容错性的消费者机制

  • 可扩展:可以增加或减少组内消费者的数量,消费者组可以通过提高消费者数量来提高处理能力,若在负载较低时,可以减少消费者数量以节省资源;
  • 容错性:如果一个消费者出现故障,Kafka会将其分配的分区重新分配给消费者组内的其他消费者

消费者组内的消费者实例会协调在一起共同消费订阅主题的所有分区,一个分区只能对应消费者组内一个消费者实例,但这个分区可以被其他消费者组消费。

三大特性

  1. Consumer Group 下可以有一个或多个 Consumer 实例。这里的实例可以是一个单独的进程,也可以是同一进程下的线程。在实际场景中,使用进程更为常见一些。
  2. Group ID 是一个字符串,在一个 Kafka 集群中,它标识唯一的一个 Consumer Group。
  3. Consumer Group 下所有实例订阅的主题的单个分区,只能分配给组内的某个 Consumer 实例消费。这个分区当然也可以被其他的 Group 消费。

设计亮点

传统的消息队列模型(点对点模型)

弊端:

  1. 消息只能被下游的一个consumer所消费,这导致下游的多个consumer获取抢占这个共享队列的消息,伸缩性差
  2. 不是持久化的,消费后会从队列中删除

Kafka如果所有消费者实例是同一Group的,则是传统的消息队列模型

订阅/发布模型

弊端:

虽然允许多个consumer同时消费消息,但每个订阅者必须消费订阅主题的所有分区,这种全量订阅的方式既不灵活,也会影响消息的真实投递效果。

kafka如果所有实例分别属于不同的group,则是订阅/发布模型

如果有这么一种机制,既可以避开这两种模型的缺陷,又兼具它们的优点,那就太好了。幸运的是,Kafka 的 Consumer Group 就是这样的机制。Consumer Group 之间彼此独立,互不影响,它们能够订阅相同的一组主题而互不干涉。 当 Consumer Group 订阅了多个主题后,组内的每个实例不要求一定要订阅主题的所有分区,它只会消费部分分区中的消息。再加上 Broker 端的消息留存机制,Kafka 的 Consumer Group 完美地规避了上面提到的伸缩性差的问题

理想情况下,Consumer 实例的数量应该等于该 Group 订阅主题的分区总数。在实际使用过程中一般不推荐设置大于总分区数的 Consumer 实例。设置多余的实例只会浪费资源,而没有任何好处

位移主题(Offsets Topic)

每个消费者在消费消息的过程中必然需要有个字段记录它当前消费到了分区的哪个位置上,这个字段就是消费者位移(Consumer Offset)。

位移主题:将Consumer的位移数据作为一条条普通的 Kafka 消息,提交到位移主题中。可以这么说,位移主题的主要作用是保存 Kafka 消费者的位移信息。

位移主题需要的特性:

  1. 高持久性
  2. 高频写操作(旧版本使用zookeeper来保存就因为不能高频写而被淘汰)

Kafka 的topic设计天然就满足这两个条件

消息格式(KV 对)

key:(要能定位到分区) topicid + 主题名 + 分区号

value:位移值

如何被创建的

当 Kafka 集群中的第一个 Consumer 程序启动时,Kafka 会自动创建位移主题,如果位移主题是 Kafka 自动创建的,那么该主题的分区数是 50,副本数是 3

不过我给你的建议是,还是让 Kafka 自动创建比较好,目前 Kafka 源码中有一些地方硬编码了 50 分区数,因此如果你自行创建了一个不同于默认分区数的位移主题,可能会碰到各种各样奇怪的问题。这是社区的一个 Bug,目前代码已经修复了,但依然在审核中

如何提交位移呢

  • 自动提交位移:

    Consumer 端有个参数叫 enable.auto.commit,如果值是 true,则 Consumer 在后台默默地为你定期提交位移,提交间隔由一个专属的参数 auto.commit.interval.ms 来控制。自动提交位移有一个显著的优点,就是省事,你不用操心位移提交的事情,就能保证消息消费不会丢失。

注意:如果你选择的是自动提交位移,那么就可能存在一个问题:只要 Consumer 一直启动着,它就会无限期地向位移主题写入消息,慢慢的就会撑爆磁盘

  • 手动提交位移

    事实上,很多与 Kafka 集成的大数据框架都是禁用自动提交位移的,如 Spark、Flink 等。这就引出了另一种位移提交方式:手动提交位移,即设置 enable.auto.commit = false。一旦设置了 false,作为 Consumer 应用开发的你就要承担起位移提交的责任。Kafka Consumer API 为你提供了位移提交的方法,如 consumer.commitSync 等。当调用这些方法时,Kafka 会向位移主题写入相应的消息。

Kafka 是怎么删除位移主题中的过期消息的呢?

Compaction(整理),对于同一个 Key 的两条消息 M1 和 M2,如果 M1 的发送时间早于 M2,那么 M1 就是过期消息。Compact 的过程就是扫描日志的所有消息,剔除那些过期的消息,然后把剩下的消息整理在一起。

Kafka 提供了专门的后台线程定期地巡检待 Compact 的主题,看看是否存在满足条件的可删除数据。

这个后台线程叫 Log Cleaner。很多实际生产环境中都出现过位移主题无限膨胀占用过多磁盘空间的问题,如果你的环境中也有这个问题,我建议你去检查一下 Log Cleaner 线程的状态,通常都是这个线程挂掉了导致的。