面试系列(五):kafka

437 阅读28分钟

消费者发送一条消息给到kafka,到消费者消费这条消息,整体的过程是怎么样的

Kafka 消息从生产者发送到消费者消费的完整流程可分为生产者发送、服务端处理、消费者消费三个阶段,具体过程如下:

一、生产者发送阶段

  1. 消息创建与序列化

    • 生产者创建 ProducerRecord 对象(包含主题、消息内容、分区键等)110。
    • 消息键(Key)和值(Value)通过配置的序列化器(如 StringSerializer)转换为字节流110。
  2. 分区路由

    • 若显式指定分区,则直接发送至该分区;
    • 若未指定分区但有键(Key),则根据键的哈希值计算分区(hash(key) % 分区数)11;
    • 若未指定分区和键,则轮询(Round-Robin)选择分区11。
  3. 批处理与缓冲

    • 消息按分区存入 RecordAccumulator 缓存区的双端队列中910。

    • 满足以下任一条件时触发批量发送:

      • 批次大小达到 batch.size(默认 16KB);
      • 等待时间超过 linger.ms(默认 0ms,表示立即发送)910。
  4. 发送至 Broker

    • Sender 线程从缓存区拉取批次数据,通过 Selector 组件封装为网络请求910。

    • 根据 acks 配置等待服务端确认:

      • acks=0:不等待确认;
      • acks=1:仅 Leader 副本写入成功;
      • acks=all:所有 ISR(同步副本)写入成功26。

二、服务端处理阶段

  1. 消息持久化

    • Leader 副本将消息追加到分区日志文件(Segment)并写入操作系统的 Page Cache57。
    • 通过 sendfile 零拷贝技术跳过用户空间,直接从 Page Cache 传输到网卡,减少 CPU 开销7。
  2. 副本同步

    • Follower 副本从 Leader 拉取消息并持久化,写入成功后更新 HW(高水位线)2。
    • 只有 ISR 中的副本均写入成功,消息才被视为 ‌已提交(Committed) ‌26。
  3. 分区与索引维护

    • 消息按偏移量(Offset)顺序存储,并生成索引文件(.index 和 .log)加速查询45。

三、消费者消费阶段

  1. 消费者组协调

    • 消费者启动时向组协调器(GroupCoordinator)注册,加入消费者组713。
    • 组协调器触发分区重平衡(Rebalance),为消费者分配分区(如 Range 或 Round-Robin 策略)78。
  2. 消息拉取(Pull 模式)

    • 消费者主动向 Leader 副本发送 FetchRequest 请求,指定分区和起始偏移量312。
    • 服务端返回批次数据,消费者反序列化消息内容12。
  3. 偏移量提交

    • 自动提交‌:周期性地将消费进度(Offset)提交至 __consumer_offsets 主题612。
    • 手动提交‌:业务逻辑处理成功后,调用 commitSync() 或 commitAsync() 提交612。
  4. 异常处理

    • 若消费者宕机,组协调器检测会话超时(session.timeout.ms),触发重平衡将分区分配给其他消费者13。
    • 若消息处理失败,消费者可选择重试或将消息移至死信队列(DLQ)

 ‌如果让你设计一个内存共享的消息中间件,如何设计

一、架构设计

  1. 核心模块划分

    • 共享内存池‌:预分配固定大小的内存区域,采用环形缓冲区结构管理
    • 消息索引服务‌:使用稀疏索引快速定位消息位置(类似Kafka的Segment设计)
    • 同步控制器‌:通过原子操作(CAS)或轻量级锁(如自旋锁)保证并发安全
  2. 进程通信模型

    mermaidCopy Code
    graph LR
     生产者 -->|写入| 共享内存区
     共享内存区 -->|通知| 消费者(信号量/事件)
     消费者 -->|读取| 共享内存区
    

二、关键技术实现

1. 共享内存管理

  • 初始化配置

    cCopy Code
    // 创建共享内存段
    int shm_id = shmget(key, SIZE, IPC_CREAT | 0666);
    char *shm_ptr = shmat(shm_id, NULL, 0);
    
  • 内存分配策略

    • 固定大小消息槽(避免碎片)
    • 双指针环形队列(读/写指针分离)5

2. 消息协议设计

字段长度说明
Magic Number4字节标识消息头(如0xMQ)1
Message Length4字节消息体长度
Timestamp8字节纳秒级时间戳
Payload变长实际消息数据

3. 同步机制

  • 生产者-消费者同步

    • 信号量(Semaphore)控制读写权限
    • 内存屏障(Memory Barrier)保证可见性
  • 故障恢复

    • 心跳检测 + 超时释放资源
    • 备份元数据到文件(防进程崩溃)

三、性能优化点

  1. 零拷贝传输

    • 直接映射共享内存到用户空间,避免内核态拷贝
  2. 批量处理

    • 支持消息打包写入(减少锁竞争)
  3. NUMA亲和性

    • 绑定内存分区到特定CPU节点(降低跨节点访问延迟)

四、注意事项

  1. 安全风险

    • 需实现权限控制(如IPC密钥校验)
  2. 扩展性限制

    • 单机方案,分布式需结合RPC扩展
  3. 调试工具

    • 提供dump_shm工具可视化内存内容

请大概讲一下kafka的部署状况?主题,分区,副本等。

Kafka的集群,是许多个Broker组成的。

一个topic会有多个分区,这个分区partition是一个概念上的东西,而一个分区里面会有一个领导者副本,多个追随者副本,这种备份制度叫做leader-follower制度。要注意的是,分区里面的不同的副本,都在不同的broker上面,原因是,一个topic里面的一条消息,只会给到一个分区,而分区里面的副本是为了实现高可用的,如果所有的副本都放到同一个broker,那这个实例挂掉了,那就等于这个分区里面的所有消息也就丢失了,也就没有什么高可用了。

领导者副本是用来和外界对接的,生产者将消息发送给领导者副本,消费者从领导者副本读取消息,而追随者副本的作用只有一个,就是从领导者副本拉取消息进行同步,这样它就可以保持与领导者副本之间的数据一致。另外,每一个分区都有一个ISR副本集合,在ISR里面的副本就是和领导者副本同步的副本,不在ISR里面的就不是和领导者副本同步的副本,另外,领导者副本也一定在ISR集合里面。只有当一个副本同步消息的速度在一段时间内都比生产者写入消息到leader副本的速度慢的时候,这个副本才会被踢出ISR集合,而这个持续的时间,是可以由我们配置的,默认值是10s。

所以,一旦领导者副本挂掉了,这就意味着也进行选举,kafka默认可以参加选举的都是ISR里面的副本,当然也可以配置为任何副本都能参加选举。

    另外,一个消息存入到分区里面的时候,它在分区里面的位置信息是由一个叫offset的位移数据来表示,从0开始递增。

你们的Kafka中的集群部署方案是怎么做的?

在说到这个问题的时候,我要先说一下我们公司的服务器的配置,是一个4核8G然后1T机械硬盘的服务器,带宽一般是千兆带宽,也就是1Gbps。

我们是将kafka都部署在独立的云服务器上的,那么1Gbps的带宽也就是一秒钟可以处理1G的数据,但是一般kafka如果用到了70%的带宽的时候,就会出现丢包的情况出现,所以我们限制了kafka峰值只能达到带宽的70%,也就是700Mb的带宽资源。但是这只是kafka的峰值设置,我们必须要给kafka做一些资源预留,防止出现峰值流量,导致把带宽打满。所以我们一般设置一台服务器能承受的带宽为峰值的1/3,也就是300Mb。

好,计算出一台服务器的kafka的带宽是200Mb以后,我们开始估算我们的业务上大概每秒需要处理的数据,经过运维监控,以及我们估算,我们本身的业务服务的请求数峰值大概是100000+,一般情况下的QPS大概是80000+,我们就假设为每秒请求kafka的数量为一般情况下的QPS大概是80000次,然后每次消息的数量我们都设置为kafka的默认最大值1M,那么估算出来每秒需要的带宽就是80000M。而我们刚刚估算了一台服务器的带宽最多承受300Mb,那么就是需要250台服务器,如果还需要做数据备份的话,要将数据额外复制两份,那就大概需要750台服务器来部署kafka。

当然,实际上并没有这么夸张,我们的估算都是按照最大值来进行估算的,实际上我们只部署了9台kafka服务器,三个分区,每个分区有三个Broker,一个是领导者副本和两个追随者副本。为什么布置这么少,一是因为实际开发中,我们的每条kafka的消息基本上不可能达到1M的情况,大部分都是只有十几k的消息,上百k的消息都很少见了。

如何保证kafka中的消息是顺序的?

要知道如何保持kafka中的消息的顺序性,我们就要先知道kafka的分区制度。Kafka是有主题topic的概念的,一个topic会分为多个区,目的很简单,是为了提供负载均衡的能力,说的更直白点,就是为了实现系统的高伸缩性,想一想,不同的分区放到不同节点的服务器上,而数据的读写也是根据分区这个粒度进行的,这样每个节点的服务器都能独立的执行各自分区的读写请求处理,这可以极大的提高kafka的吞吐量。

而要实现消息的顺序性,我们还要知道kafka分区的一个特点,那就是一个消息只能在一个分区里面存储。

Kafka为消息的分发到各个分区提供了三个策略,第一个策略,是轮询,也就是说如果我有三个分区A,B,C,那么第一个消息给A,第二个消息给B,第三个消息给C,然后第四个消息又给到A,依次轮询下去。它的优点就是可以将消息很均匀的分发给所有的分区,是最合理的分发策略。

第二个策略是随机,也就是说kafka会将消息随机放到一个分区,这个策略虽然也是为了将消息均匀的给到所有的分区,但是实际上,它的性能是比轮询要差的。

第三个策略就是通过key分发消息的策略。生产者在生产消息的时候,可以设置一个key值,kafka保证相同key值的消息会分发到同一个分区。这也是kafka中如何保持消息是顺序的方法。

比如说,在我们业务处理中,监控数据库的blog日志,然后同步到es,这里就需要保证消息的顺序性,于是我们就设置了一个key值为公司名+mysql,这样的话,所有关于监控binlog的消息都是顺序性的了。

 

在netty中,在kafka中都有零拷贝,请大概介绍了零拷贝的意思?

Kafka零拷贝的本质是:

  1. 网络传输‌:通过sendfile实现‌磁盘→PageCache→网卡‌的直达路径,消除用户态复制。
  2. 文件写入‌:通过mmap实现‌用户进程直接操作PageCache‌,减少内核态切换。
  3. 资源复用‌:结合PageCache与批量处理,最大化减少CPU与内存开销。
    Kafka的‌零拷贝(Zero-Copy) ‌ 是一种通过减少数据在用户态和内核态之间的冗余复制来提升I/O效率的核心技术,其原理主要依赖操作系统底层优化与Kafka的针对性设计。以下是关键实现机制:

一、传统I/O的瓶颈(4次拷贝+4次上下文切换)

  1. 磁盘 → 内核缓冲区(Page Cache)

    • DMA技术完成,无需CPU参与。
  2. 内核缓冲区 → 用户缓冲区

    • CPU拷贝,用户态与内核态切换(read()调用)。
  3. 用户缓冲区 → Socket缓冲区

    • CPU二次拷贝,再次切换至内核态(write()调用)。
  4. Socket缓冲区 → 网卡(NIC)

    • DMA传输至网络3。
      问题‌:CPU参与两次冗余拷贝,上下文切换频繁,消耗大量资源。

二、零拷贝的核心原理:跳过用户态

1. sendfile系统调用(网络传输优化)

  • 流程‌:

    mermaidCopy Code
    graph LR
      A[磁盘文件] -->|DMA| B[内核缓冲区 PageCache]
      B -->|CPU拷贝| C[Socket缓冲区]
      C -->|DMA| D[网卡]
    
    • 仅2次DMA + 1次CPU拷贝‌,无用户态参与。
  • Kafka应用‌:

    • FileChannel.transferTo()底层调用sendfile()
    • Broker直接将日志文件从PageCache发送到网卡,避免用户态数据复制。

2. mmap内存映射(文件写入优化)

  • 原理‌:

    • 将磁盘文件映射到进程虚拟地址空间,用户进程直接读写内核缓冲区(PageCache)。
  • Kafka应用‌:

    • Producer写入消息时,通过mmap将日志文件映射到内存,减少write()调用的数据复制。

三、Kafka的协同优化设计

  1. PageCache(页缓存)利用

    • 消息写入/读取优先经过内核缓冲区,后续消费可直接复用缓存数据。
  2. 批量消息处理

    • Record Batch为单位传输,减少零拷贝调用次数。
  3. 消费者场景优化

    • 消费者读取时,Broker直接通过sendfile将PageCache的数据发送到网络,无需加载到应用层。

四、性能提升对比

场景传统I/O零拷贝
拷贝次数4次(2次CPU参与)2-3次(CPU仅1次)
上下文切换4次2次5
10个消费者的总拷贝40次11次(1+10)

五、注意事项

  1. 仅适用于消费者拉取消息

    • Producer发送消息仍需经用户态校验,无法零拷贝。
  2. 小文件性能有限

    • sendfile仍需一次CPU拷贝(PageCache→Socket缓冲区),小文件收益较低。
  3. 依赖操作系统支持

    • Linux的sendfilemmap是基础,Windows需用TransmitFile

总结

Kafka零拷贝的本质是:

  1. 网络传输‌:通过sendfile实现‌磁盘→PageCache→网卡‌的直达路径,消除用户态复制。
  2. 文件写入‌:通过mmap实现‌用户进程直接操作PageCache‌,减少内核态切换。
  3. 资源复用‌:结合PageCache与批量处理,最大化减少CPU与内存开销。

请大概讲解一下kafka中消息的传递过程中的压缩和解压缩。

先说一下kafka的压缩。Kafka的压缩过程主要是集中在生产者端,目的很简单,是为了节省带宽,那么压缩是怎么压缩的呢?我们知道,kafka中一个消息集合中有非常多的消息,kafka一般不会直接操作消息,而是操作消息集合。在kafka的1.1版本之前,kafka是将消息集合中的消息压缩,然后将它存储在消息集合的消息体字段中,但是在1.1版本之后,就是将整个消息集合都压缩了。

当然,kafka也可以在Broker上进行压缩,大部分情况下,其实Broker都不会将消息进行压缩,但是还是有例外,如果服务端和生产者使用了不同的压缩算法的时候,服务端需要将生产者传递过来的消息解压后,然后按照自己的压缩算法进行压缩。还有一种特殊情况就是,kafka里面保存了不同版本的类型的消息,这个时候为了兼容老版本的消息,kafka必须将消息解压,将新消息的格式转换为老消息的格式,然后再进行压缩。

那么什么时候解压缩了,其实刚刚我也说了,Broker服务端在进行压缩算法转换,以及消息格式转换的时候,会进行解压缩,而且即便没有这两种情况,Broker服务端为了校验数据,也会将消息解压,然后进行校验,校验完成之后再将消息压缩。但是,大部分时候,解压缩的行为都发生在消费端,消费端获取到消息后,会从消息体中获取到消息的压缩算法,然后将消息解压。

我们业务使用的算法是GZIP压缩算法。

 

Kafka的消息丢失都有哪些情况?

一般来说,kafka出现消息丢失的情况有三种。

第一种,是我们的kafka的生产者丢失了消息,比如说因为网络抖动,或者消息格式不对,或者消息太大,导致了生产者发送给Broker的消息失败了。这种情况的解决方案是生产者发送消息的时候要有方法的回调,保证消息已经到了kafka的服务端并且已经持久化了,并且最好是将acks=all,这样能保证这个消息所在分区的所有副本都要接受到消息。

第二种,则是我们的消费端丢失了数据,比如说我们的消费端还没有消费完消息,我们kafka服务端的消费位移就已经移动了,导致了kafka服务端误以为我们消费成功了,但是实际上消费端因为程序问题而消费失败。解决方案很简单,要保证程序代码消费成功了才手动提交位移。

第三种,就是我们的kafka服务端出了问题,比如说我们的某个分区的领导者副本宕机了,这个时候如果分区里面出现了选举的情况,也会导致需要存储到这个分区的消息持久化失败。解决方案是让kafka的生产者进行重试。

其实还有一种特殊的情况,就是我们在为一个topic增加一个分区的时候,生产者比消费者先知道这个分区的诞生,于是将消息发送给这个新分区存储,消费者后面感应到了这个新的分区,但是我们关于kafka的配置是新分区从最新位移处开始读取消息,于是就会导致生产者在这之前发送给这个分区的消息就丢失了。解决方案就是修改配置,将auto.offset.reset=latest修改为auto.offset.reset=ealiest,这样消费者感应到新分区的时候,会从第一条消息开始消费消息。

Kafka中的消费者组是什么意思?

消费者组就是就是多个消费者组成一个组,这个组内的所有消费者共享一个id,就是Group Id,组内的所有消费者协调在一起来消费订阅主题的所有分区。要注意的是,一个分区只能被组内的一个消费者消费,但是可以被其他组的消费者消费。

如果所有的实例都属于同一个消费者组group,那么就意味着这实现的是点对点的消息队列,如果消费者实例被分为了不同的消费者组,那么实现的就是我们所知道的发布/订阅模式。

消费者组的优点就在于,如果是点对点的模式,每一个分区都只给到一个消费者消费,就不需要消费者进行抢夺消息消费,如果是发布/订阅模式,传统的消息引擎,每一个消费者都必须订阅一个主题里面的所有分区,性能损耗太大,不灵活,而消费者组里面,一个消费者会订阅一个主题的所有分区,但是一个消费者只需要负责部分分区就可以了。

那么如何设置一个消费者组的消费者数量呢?一般来说,假如我们设置的一个topic的分区数量是9个,那么我们最好就设置一个消费者组内有9个消费者,这样是最好的思路,可以最大限度的实现高伸缩性。但是实际情况我们都知道,我们的一个消费者实例,实际上就是我们的一个实例服务,一般我们的服务搭建,基本上都是三个实例就够了,所以一个消费者里面有3个消费者也可以,每个消费者可以承担3个分区的消息。

Kafka中的位移主题是什么东西?

Kafka中_consumer_offset位移主题,其实就是用来保存consumer消费者的位移数据的,kafka会将消费者的位移数据,作为一条普通的kafka消息,保存到_consumer_offset主题中。

我们知道,在早期的kafka版本中,是使用zookeeper来保存消费者的位移数据的,但是zookeeper有一个问题,就是它本身是不适用于这种高频的写操作的,性能会变得不是很好,于是在0.8.2.x版本的kafka中,不再使用zookeeper来保存位移数据,而是使用kafka自己内部的_consumer_offset主题来保存消费者的位移数据。

这个位移主题里面的消息,是key-value形式的,key值=消费者组id+topic名字+分区号,值就是这个分区里面的消息的位移值。

那么,位移主题里面的消息是怎么生成的,并且写入到_consumer_offset主题的呢?kafka给我们提供了两种方案,第一种自然就是kafka会在消费者拉取一个分区的消息的时候,自动将关于位移消息的变更存入到_consumer_offset主题中。这种方案比较呆板,而且可能会导致消息丢失的情况出现。还有第二种方案,就是我们消费者自己设置手动提交位移,kafka提供了一个api给我们,我们可以调用这个接口将位移数据写入_consumer_offset主题,但是具体是什么时候写入,那要看我们自己。

手动提交位移的方式有两种,同步或者异步。

Kafka中的rebalance重平衡指的是什么?

我们知道,kafka的消费者可以组成一个消费者组,来订阅一个主题下的所有分区,并且形成一个共识,那就是一个分区只能被消费者组里面的一个消费者订阅。但是,如果这个消费者主题下的消费者中有一个消费者退出了,那么就会引发这个消费者组里面订阅的主题的所有的分区,在协调者的帮助下的重新分配。这就是我们所说的rebalance重平衡。

而协调重平衡的协调者,就是我们的Coordinator,主要是负责消费者组的重平衡和组成员管理,它由一个分区的领导者副本所在的broker生成。

而且要注意的是,在重平衡的过程中,所有的消费者实例是不能消费消息的,这对消费者的TPS影响很大,而且如果消费者组里面的消费者很多,会导致重平衡的时间很长。

当前来说,kafka并没有为重平衡提供一个合适的解决方案,因此我们能做的,就是尽量避免重平衡。

一般来说,发生重平衡的原因主要有三点,一是我们消费者组里面的成员发生了变化,二是topic的数量发生了变化,三是topic的分区发生了变化。

第二点第三点都是我们自己有意这么做的,无法避免也不需要避免,但是第一点,消费者组里面的成员发生了变化,那有可能就是我们的有一个节点服务,被kafka认为已经宕机了,所以踢出了消费者组。

如果服务真的宕机了,那也就是我们的业务系统的稳定性问题,但是也有的是我们kafka服务器误认为我们的服务宕机了。

Kafka的消费者有一个参数设置,叫session.timeout.ms,该参数默认值是10s,也就是说,如果kafka在10s内没有收到消费者发生的心跳消息,那么就会将这个消费者踢出消费者组,还有一个参数是heartbeat.interval.ms,这个参数的意义就是消费者多久发送一次心跳。

因此我们在设置这两个参数的时候,要将session.timeout.ms值设置为heartbeat.interval.ms的三倍,这样能保证在这个时间间隔内有3次心跳,不至于因为网络原因导致心跳发送失败。

还有一个参数是max.poll.interval.ms,这个参数的默认值是5分钟,如果消费者在5分钟内没有消费完poll拉下来的消息,那么kafka也会将这个消费者踢出消费者组。这个时候我们要看我们自己的业务,消费一波消息的时间大概要多久,然后这个参数的值一定要大于这个值。

 

Kafka中的commitFailedException是因为什么原因?

消费者端报这个错误,其实就是位移提交的时候出现了异常或者错误,而且还是不可恢复的严重异常。

出现这种异常的情况主要还是消费者消费消息的时间太久了,导致没有来得及拉取下一批消息,导致报错。

我们都知道,kafka可配置的参数max.poll.interval.ms,这个配置的意思是消费者两次poll消息的时间间隔,当消费者两次poll消息的时间间隔超过了这个配置的时候,那么就会报错。

除了这个配置以外,还有一个配置参数max.poll.records,这个参数设置的就是消费者一次poll消息的条数。因为消费者两次poll消息的时间间隔太长,那肯定就是因为这一批消息的消费时间很长,要么提高这个间隔时间的参数的值,要么就减少这个拉取消息数量的这个参数的值。

当然,最好的方法还是去提高自己代码的质量,将消息消费所要损耗的时间减短,但是如果做不到的,那么也可以手动创建多线程来消费,但是这有个问题,就是多个线程之间的位移提交的问题,这个就很难处理。

当然,在修改这个参数的时候要注意,如果消费者因为消费消息的时间太长而阻塞了,导致没有机会发生心跳消息给kafka服务端,那么会引发重平衡,也是一个很大问题。当然,在kafka的1.0版本开始,心跳的发送是由独立的线程操作了,不会受到主线程阻塞的影响。

要注意的是,发生commitFailedException都是因为消费者的位移提交是手动的,如果是kafka服务端自动提交位移,那么就不会报这个错误。

 

Kafka的幂等性如何保持?

Kafka的幂等主要是有两种情况,一种是生产者和broker之间的幂等,一种是broker和消费者之间的幂等。

生产者和broker之间的幂等,kafka其实是提供了三种承诺,第一种承诺叫最多一次,也就是说生产者只向kafka服务器发送一次消息,但是有可能会导致消息丢失。第二种承诺叫至少一次,kafka保证消息不会丢失,但是生产者有可能重复发送消息给kafka服务器。第三种承诺是精确一次,也就是说kafka保证消息不会丢失,也不会重复发送。

因为性能的考虑,kafka现在默认的设置至少一次,那么为什么这种情况会出现重复发送呢?因为对于生产者而言,只有当消息已经被kafka服务器接收到了,并且kafka服务器发送消息给生产者通知收到了,才能当作是消息发送成功。但是在kafka服务端返回消息给生产者的过程中,可能网络问题,生产者没有收到消息,所以导致了生产者的重复发送消息。

那么如何保证kafka服务端的消息是幂等的呢?在生产者发送消息的时候,其实有一个我们可以理解为版本号的参数SequenceNumber,这个参数是每个topic下每一个分区都有的参数,这个参数是递增的,也因此kafka服务端可以根据这个参数判断消息是否是重复发送的。

生产者和broker实例之间的幂等是实现了,那么broker和消费者之间的幂等怎么实现呢?其实,这只能是通过我们的业务代码自己实现。

消费者组里面的消费者进度是怎么监控的?

首先我们要知道,为什么要监控消费者的消费进度?一般来说,我们讲的消费者进度,就是我们的消费者Lag,这个Lag的值,其实就是消费者当前落后于生产者的速度。比如生产者生产了100w条消息,但是消费者只消费了80w条,那么我们的Lag就等于20w。

一个正常的消费者,它的Lag值应该是无限接近于0的。如果一个消费者的Lag值很大,意味着没有办法跟上生产者生产消息的速度,如果持续下去,Lag值会越变越大,甚至导致消费者消费太慢了,导致它要消费的消息已经不再操作系统的缓存中了,这样,消费者不得不从磁盘中读取消息,这将情况进一步恶劣化了。

我们监控这个消费者进度Lag的方法有多种,但是在实际开发中,我们使用的是kafka社区提供的在consumer客户端的api,lagOf()方法,这个方法可以返回当前消费者的Lag值。但是这个方法只在kafka2.0版本之后才能用,而且它没有办法集成到我们当前的框架中,比如Zabbix或者Grafana。

因此我们可以使用kafka默认提供的JMX监控指标来监控消费者的Lag值,而且使用JMX监控,不止是可以监控到Lag值,还能监控到lead值。Lead值是当前消费的位移和分区当前第一条消息位移的差值。我们都知道,kafka的消息会定期删除消息,这也就意味着分区第一条消息的位移的值会越来越大,那么如果lead值越来越小,那么就意味着可能kafka下一次删除消息,可能会删除掉我们没有消费的消息,这会造成消息丢失,这是要小心的。

Kafka客户端发送一个消息,是如何存储到kafka中的呢?

Kafka里面有一个Acceptor线程,这个线程是专门用来做请求分发的,而kafka中有专门用来处理请求的线程池,叫做网络线程池。当一个请求过来的时候,kafka中Acceptor线程会将这个请求采用轮询的方式分发给网络线程池中的一个线程,这个时候网络线程不会去真的处理这个请求,而是将这个请求放到一个共享请求队列里面。

而kafka内部还有一个IO线程池,专门用来做IO操作,这个线程池里面的线程是专门来解决这个共享请求队列里面的请求的,它会从请求队列里面取出请求,去磁盘写入数据或者读取数据。

当请求处理完成之后,IO线程会将生成的响应发送到网络线程池的一个队列中,然后这个请求对应的网络线程会将这个响应返回给我们的客户端。

 

Kafka中的控制器以及zookeeper的关系?

Kafka的控制器是用来管理和协调整个kafka集群的,集群中的任意一台broker都能成为控制器,关键就是在于哪一个broker更快的在zookeeper上创建一个/controller节点,哪一个broker就会被指定为控制器。

控制器具体的工作有以下几点:

1.      主题管理,topic的创建和删除,以及topic下分区的增加或者减少。

2.      集群成员的管理,broker的创建,关闭和宕机,都由控制器管理。

3.      副本管理,分区上的所有副本都由控制器管理和分发。