消息队列-kafka完结

基本概念和操作

基本概念

简单概念

:::color4

  1. Broker:如果将kafka比喻成数据仓库网络,那么Broker就是网络中的仓库节点,比如快递站,在该节点内可以独立运行,并且多个Broker可以连接起来,构建kafka集群
  2. Topic:数据存储时的分类标签,即在存储时需要指定对应的topic类型(比如订单消息类型与物流消息)
  3. Consumer:消息的消费者,这里与RocketMq的区别是这里的消费者是主动获取消息,而RocketMq是新增一个代理,先通过代理主动获取消息,再利用代理推消息给消费者,使得消费者看起来是被动接收
  4. Partition:实际存储数据的 "小分区",可以将一个topic多分存储到不同的partition,好处是:1️⃣如果有消息丢失,一整个topic不会一起丢失 2️⃣可以扩大存储内容,比如需要存储100T的数据,那么可以多分,每个partition只需要存储10T即可【topic是概念的,partition才是物理上的存储】

:::

  • Topic(主题)是逻辑上对消息的分类,比如 "物流""订单" 这两个 Topic,分别对应物流相关、订单相关的消息。
  • 然后,Partition(分区)是物理上存储消息的单元。一个 Topic 可以有多个 Partition,像 "物流" Topic 有 partition1、partition2,"订单" Topic 有 partition3。这些 Partition 可以分布在同一个 Broker 上(图里就是这种情况),也可以分布在不同 Broker 上,具体看集群部署和需求。
分区

:::color1

即:在kafka中,分区是真正存储数据的地方,生产者在生成消息时,可以指定topic和分区 ,同时不同的分区也可以分布在不同的Broker上,即不同的服务器

:::
分区详解

  1. 分布式存储能力

当一个主题(Topic)的消息量非常大时,仅靠单一存储单元难以承载。通过将 Topic 划分成多个分区 (图中的 P1、P2、P3、P4 等),可以把消息分散存储到不同的分区中。

这些分区又可以分布在 Kafka 集群的不同 Broker (服务器节点)上,从而实现消息的分布式存储 ,突破了单节点存储容量和性能的限制。

  1. 并行处理与并发存储

不同的生产者(图中的 Producer client 1、Producer client 2)可以同时向不同的分区发送消息 。比如 Producer client 1 向 Partition 1 发送消息,Producer client 2 可以同时向 Partition 3、Partition 4 发送消息。这种机制让 Kafka 具备了并发存储消息的能力 ,提升了整体的消息处理吞吐量,能更好地应对高并发的消息生产场景。

  1. 负载均衡

分区的存在使得消息生产和存储的压力可以分散到多个分区甚至多个 Broker 上 。不会出现所有消息都集中在一个存储单元,导致该单元负载过高,而其他单元闲置的情况,实现了负载均衡 ,保障了系统的稳定和高效运行。

副本
  • 保障数据可用性:当存储数据的分片/分区(Partition)所在节点出现故障时,由于存在副本(多个分片副本),数据不会丢失,系统仍能通过副本对外提供服务,提升了 Kafka 系统的可用性。
  • 解决数据一致性问题(同时也带来挑战):通过主(Leader)从(Follower)架构,Leader 负责数据的读写操作,并将数据同步给 Follower。虽然副本存在可能导致部分副本写数据成功、部分失败的一致性问题,但整体上通过主从同步机制,尽可能保证数据在多个副本间的一致性,为后续主从选举等操作提供基础,确保系统在故障切换后数据仍能保持相对一致。

与其他概念的联系

  • 与 Partition(分区)的联系 :副本是基于 Partition 存在的,每个 Partition 可以有多个副本 ,这些副本分布在不同的 Broker 上,进一步增强了 Partition 的可靠性和可用性。
  • 与 Broker(节点)的联系 :副本分布在不同的 Broker 上,不同的 Broker 可以承载同一个 Partition 的不同副本(Leader 或 Follower),Broker 是副本的物理承载节点,多个 Broker 共同支撑起副本机制,实现数据的分布式存储与备份。
  • 与 Leader/Follower(主从)的联系副本分为 Leader 副本和 Follower 副本 ,Leader 副本处理读写请求并同步数据给 Follower 副本,Follower 副本接收同步数据,二者共同构成副本的主从架构,是副本机制的核心组成部分,保障了数据在多副本间的流转与一致性维护。
  • 与 Consumer(消费者)的联系消费者从 Leader 副本读取数据 ,因为 Follower 不提供读写(主要为保证一致性),所以消费者的读操作依赖于 Leader 副本,而副本的存在 (尤其是Leader 故障时能选举新 Leader )保障了消费者能持续从可用的 Leader 副本读取数据,不中断消费流程。

![](https://i-blog.csdnimg.cn/img_convert/784cc074eee76aef7c02ad9c3c6fb9a5.png)

常用命令

传播消息

单播消息(group.id相同 )

即在同一个消费组里,只会有一个消费者能够消费到某个topic中的消息

多播消息(group.id 不相同)

多个消费组在同一个topic下,那么当生产者向topic发送消息时,多个消费组都可以接收到消息

消费组

  1. CURRENT-OFFSET(当前偏移量)
  • 含义 :表示消费组当前已经消费到的消息在分区中的偏移量。简单来说,就是消费组在对应的分区里,已经处理过了多少条消息,这个数字是从 0 开始计数的。它反映了消费组的消费进度。
  • 举例 :假设你订阅了一个名为 <font style="color:rgb(0, 0, 0);">news-topic</font> 的 Kafka 主题,这个主题有一个分区。你的消费组是 <font style="color:rgb(0, 0, 0);">reader-group</font> ,当 <font style="color:rgb(0, 0, 0);">CURRENT-OFFSET</font> 的值是 100 时,就意味着 <font style="color:rgb(0, 0, 0);">reader-group</font> 这个消费组已经成功消费了这个分区里从第 0 条到第 99 条,总共 100 条消息。
  1. LOG-END-OFFSET(日志结束偏移量)
  • 含义 :也叫水位(HW),代表主题对应分区中当前最新消息的结束偏移量 ,也就是分区里目前总共存储了多少条消息(从 0 开始计数)。它反映了分区中消息的总量。
  • 举例 :还是以 <font style="color:rgb(0, 0, 0);">news-topic</font> 主题和它的一个分区为例,如果 <font style="color:rgb(0, 0, 0);">LOG-END-OFFSET</font> 的值是 150 ,这就表示在这个分区里,目前一共有 150 条消息(从第 0 条到第 149 条 )。
  1. LAG(滞后量)
  • 含义 :是 **<font style="color:#DF2A3F;">LOG-END-OFFSET</font>**减去 **<font style="color:#DF2A3F;">CURRENT-OFFSET</font>**的值 ,用来衡量消费组消费消息的速度和分区中消息产生速度之间的差距 ,即还有多少条消息没有被消费组处理。如果 <font style="color:rgb(0, 0, 0);">LAG</font> 的值为 0,说明消费组已经把分区里当前所有消息都消费完了,没有积压;如果值越大,说明积压未消费的消息越多。
  • 举例 :在 <font style="color:rgb(0, 0, 0);">news-topic</font> 主题和 <font style="color:rgb(0, 0, 0);">reader-group</font> 消费组这个场景中,<font style="color:rgb(0, 0, 0);">CURRENT-OFFSET</font> 是 100,<font style="color:rgb(0, 0, 0);">LOG-END-OFFSET</font> 是 150 ,那么 <font style="color:rgb(0, 0, 0);">LAG = 150 - 100 = 50</font>,这就表示 <font style="color:rgb(0, 0, 0);">reader-group</font> 这个消费组还有 50 条消息没有消费。

__consumer_offsets

是 Kafka 内部用于存储消费者组 (Consumer Group)消费偏移量(offset)的主题(Topic)

原理:消费者会定期将消费分区的偏移量 提交给 <font style="color:rgba(0, 0, 0, 0.85);">__consumer_offsets</font> 这个内部主题

作用:

  1. 支持消费者故障恢复和再均衡:当消费者出现故障重启时,可以从这里获取对应的偏移量,即可继续由上次位置继续进行消费
  2. 便于管理和监控消费状态 :通过查看 <font style="color:rgba(0, 0, 0, 0.85);">__consumer_offsets</font> 中记录的偏移量信息,运维人员和开发者可以了解各个消费者组的消费进度,判断是否存在消息积压等情况。比如,

稀疏索引

稀疏索引的作用是通过**「跳跃式记录关键位置」**,减少查询时需要遍历的数据量,尤其适合顺序存储的数据。
举例

假设数据按偏移量 1~9 顺序排列(类似数组的连续存储):
1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9

没有索引时 :要找偏移量 5 的位置,只能从 1 开始依次往后查(1→2→3→4→5),共需 5 步。

有稀疏索引表 (1,4,8) 时

  1. 先查索引表,发现 4 < 5 < 8,说明目标 5 一定在索引 4 和 8 之间
  2. 于是直接从索引 4 的位置开始往后找,只需查 4→5 这 2 步就够了

相当于索引帮你「跳过了前面 1→2→3 的无效遍历」,尤其当数据量很大时(比如偏移量到 10000),稀疏索引能大幅减少查询步骤(比如索引表是 1,100,200...,查 500 时直接定位到 200 开始找,而不是从 1 遍历)。
![](https://i-blog.csdnimg.cn/img_convert/3057887c1473d497508a2f570b8931e6.png)

Kafka集群

集群的搭建

Kafka 集群是由多个 Broker 节点组成的,每个 Broker 都可以接收 生产者发送的消息,存储 消息,并为消费者提供消息读取服务。之所以按照上述方式配置就形成了集群,主要是基于以下几个关键方面:
为什么称之为集群

唯一的 Broker 标识(broker.id

broker.id是每个 Broker 在集群中的唯一身份标识。在这个配置里,kafka1的broker.id是 10 ,kafka2的是 11,kafka3的是 12 。Kafka 集群正是通过这些不同的 broker.id****来区分各个 Broker 节点 。有了唯一标识,在集群内部进行诸如选举分区首领(Leader)、同步数据等操作时,就能准确识别和管理每个 Broker ,就好比每个员工都有唯一工号,方便公司管理和安排工作。

不同的监听地址(listeners)

每个 Broker 都配置了不同的监听地址和端口,kafka1监听localhost:9093,kafka2监听localhost:9094,kafka3监听localhost:9095。这意味着它们可以独立接收来自生产者(Producer)和消费者(Consumer)的请求,并且相互之间不会因为端口冲突等问题而影响通信。就像不同的门店有各自的地址,顾客(生产者和消费者)可以分别前往不同门店(Broker)办理业务。

独立的日志存储目录(log.dirs)

每个 Broker 的log.dirs配置指定了独立的日志存储路径,kafka1的日志存储在/Users/muse/Lesson/ServerContext/kafka-cluster/kafka1/kafka-logs ,kafka2和kafka3也类似。这保证了各个 Broker 产生的消息日志是分开存储的,它们可以独立管理自己的消息数据,互不干扰。例如,当一个 Broker 出现故障时,不会影响其他 Broker 日志数据的正常读写和管理。

集群通信与协同

在启动后,这些配置好的 Broker 会通过 Kafka 内部的通信协议自动发现彼此 ,然后进行通信和协同工作。它们会参与 1.分区副本的管理 ,比如决定哪些 Broker 上的分区副本作为首领(Leader)副本,哪些作为跟随(Follower)副本 。2.当生产者发送消息时,消息会根据分区策略被分发到不同 Broker 上的分区;消费者消费消息时,也能从多个 Broker 上拉取消息,实现负载均衡和高可用性 。

比如,在一个高并发的电商订单处理场景中,多个订单消息 可以被均匀地发送到集群中不同的 Broker 上存储,消费者也能从不同 Broker 上高效地获取订单消息进行处理。

通过对broker.id、listeners、log.dirs等关键参数的配置,让这三个 Kafka 实例各自成为独立的 Broker,并且能够相互通信、协同工作,共同承担消息的存储和处理任务,从而构成了一个 Kafka 集群。
![](https://i-blog.csdnimg.cn/img_convert/3649720c1eeb1b5434b62ec2e9727491.png)

集群的启动

多分区与多副本

一个主题的多个分区可以存储在不同的Broker中,同一分区的Leader副本和Follower副本可以存储在不同的Broker中,避免数据丢失

多分区消费者组

1.一个partition只能被一个消费组中的一个消费者消费,但是多个消费者组,不是有多个消费者可以同时消费partition吗 那么会不会因为同时有多个消费者造成消息的非顺序呢?

答:不会,因为不同消费者组的偏移量互不影响【消费组 A 用于处理订单的业务逻辑 ,消费组 B 用于对订单数据进行统计分析 ,它们同时消费订单主题的某个分区,消费组 A 按照自己的顺序 处理订单,消费组 B 也按照自己的顺序 拉取和分析订单数据,二者不会相互干扰消息顺序

2.因为一个partition只能被同一个消费组者中的一个消费者消费,所以当消费组中的消费者>partition时,就会造成有一些消费者空闲

mq流程讲解

  1. 在创建主题topic时
  • 指令<font style="color:rgb(0, 0, 0);">bin/kafka-topics.sh --create --bootstrap-server <server-address> --replication-factor <factor> --partitions <num> --topic <topic-name></font>
  • 作用 :在 Kafka 集群中创建新的主题。通过 <font style="color:rgb(0, 0, 0);">--replication-factor</font> 指定副本因子 ,确定每个分区的副本数量,以提升数据的可靠性和容错性;<font style="color:rgb(0, 0, 0);">--partitions</font> 用于设置主题的分区数量(同一个分区存储着消息和其副本) ,帮助实现消息的分布式存储和并行处理;**<font style="color:#DF2A3F;">--topic</font>**则明确要创建的主题名称

:::color1

分区创建之初,Kafka 会从该分区的多个副本中选举出一个作为 Leader 副本,其他副本成为 Follower 副本(如果Leader副本故障,则会从Follower选举出一个作为Leader副本)

:::

  • 示例<font style="color:rgb(0, 0, 0);">bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 3 --partitions 2 --topic orders</font> ,表示在本地 Kafka 集群(地址为 <font style="color:rgb(0, 0, 0);">localhost:9092</font> )上创建一个名为 <font style="color:rgb(0, 0, 0);">orders</font> 的主题,该主题有 2 个分区,每个分区有 3 个副本。
  1. 发送消息(生产者)
    • 指令<font style="color:rgb(0, 0, 0);">bin/kafka-console-producer.sh --bootstrap-server <server-address> --topic <topic-name></font> ,然后在控制台输入消息内容回车发送。
    • 作用
      • 1️⃣生产消息时,,可以指定存储在哪个主体和对应的哪个分区
      • 2️⃣消息会先存储在Leader副本,然后kafka再自动同步到Follower副本中
    • 示例<font style="color:rgb(0, 0, 0);">bin/kafka-console-producer.sh --bootstrap-server localhost:9092 --topic orders</font> ,进入生产者控制台,之后输入的消息都会发送到 <font style="color:rgb(0, 0, 0);">orders</font> 主题。
  2. 集群情况下

1️⃣同一个分区上的Leader和Follower副本会存储在不同的Broker中,避免相同数据全部丢失【但单机Broker的同一分区的不同副本仍然存在一起,因为不能分了】

同一个分区的多个副本会尽量分布在不同的 Broker 上。比如<font style="color:rgb(0, 0, 0);">orders</font>主题分区 0 的 3 个副本,可能分别存储在 Broker A、Broker B、Broker C 上。这样当某个 Broker 出现故障时,其他 Broker 上的副本可以继续提供服务,保证消息的读写操作不受太大影响。

  1. Rebalance机制
  1. Controller
  • 成为集群控制核心:集群里的 Broker 在 ZooKeeper(ZK)中创建临时序号节点,序号最小(最先创建)的节点会成为 Controller,负责管理整个集群所有分区和副本的状态。
  • 处理 Leader 副本故障 :当某个分区的 Leader 副本出现故障 时,Controller 会为该分区选举出新的 Leader 副本,保证分区的正常读写操作不受影响。
  • 管理 ISR 集合变化:若检测到某个分区的 ISR(In-Sync Replicas,同步副本)集合发生变化,Controller 会通知所有 Broker 更新相关元数据信息,确保集群各节点对分区副本同步状态有一致认知。
  • 同步新分区信息 :使用<font style="color:rgb(0, 0, 0);">kafka-topics.sh</font>脚本为某个 Topic 增加分区数量时,Controller 会让新分区被其他节点感知到,使整个集群能协同处理新分区的消息。
  1. 高水位和LEO

高频面试题

1.如何防止消息丢失

答案

答:发送方是利用**确保消息备份成功**来保证消息不丢失,

接收方是**将自动提交改为手动提交**,即将消费消息后的 offset 移动改为手动提交,避免有时候因为网络抖动问题造成消息消费中断,但是如果是自动提交设置就已经移动了 offset,造成消息丢失,这就是自动提交的弊端

ack介绍:发出消息持久化机制参数

acks=0: 表示producer不需要等待 任何broker确认收到消息的ACK回复,就可以继续发送下一条消息。性能最高,但是最容易丢失消息

acks=1: 表示至少等待leader已经成功将数据写入本地log,但是不需要等待所有follower都写入成功,就可以继续发送下一条消息。 这种情况下,如果follower没有成功备份数据,而此时leader又挂掉,则消息就会丢失。

acks=-1: 表示kafka ISR列表中所有的副本同步数据成功,才返回消息给客户端,这是最强的数据保证。

min.insync.replicas 这个配置是用来设置同步副本个数的下限的, 并不是只有 min.insync.replicas 个副本同步成功就返回ack。而是,只要acks=all就意味着ISR列表里面的副本必须都要同步成功。

2.如何防止消息的重复消费?

答案

幂等性就是指:无论你重复操作多少次,产生的结果都和只操作一次是一样的。(即 1 的 n 次方=1,0 的 n 次方=0,n 为 1 和 0 时,符合结果,即为幂等元)

比如生成 order.id,将这个 id 在缓存中缓存 10min,这样子就只会产生一个 id

3.如何做到顺序消费

答案

解释:

发送方:非 0 是因为避免导致消息丢失,如果消失丢失,即使后部分是有序的,那么整体也是无序的,

接收方:由于一个 topic 可以有多个消费者组同时消费一个分区,所以这里面都顺序无法保证,但是可以让只配置一个消费者组去消费

4.如何解决消息积压问题

答案

消息积压:则利用加快消费能力解决,

1️⃣可以分为提升一个消费者(即利用多线程),

2️⃣提升整体消费者(比如增加 消费者**组** 数量,注意因为信息是存在 partition 中的,而一个 partition 中的消息只能被一个消费者组中的一个消费者消费,所以增多了消费者组,那么就得增加 partition 的数量,使得不会造成某消费者组中的消费者空闲等待)

3️⃣设置消息的过期时间段(比如订单应该 15min 馁处理,那么在处理前先判断是否过期快速排查)

5.如何实现延迟队列

**延迟队列是一种允许消息在指定的延迟时间之后才被投递和消费的消息队列。 **

答案

这里为什么实现了呢?

  1. 先利用一个 topic 去存储需要被延迟业务实现到队列(比如**取消订单业务**逻辑需要在 30min 后再实现该逻辑,那么就得等待 30min 之后执行)
  2. 在生产消息时带上 creat_time 记录创建时间
  3. 在消费者消费时,利用** now_time - creat_time**判断是否>30min,如果是>,那么就去执行业务逻辑,如果是<30min,那么就无需消费,**保持 offset,暂停消费**(因为根据顺序存储的原理,后面的更不会>30min)
  4. 接着比如每 10s 去通过前面记录的 offset 继续判断并且消费信息,即再去判断是否>30min,重复步骤3、4.【以达到延迟的效果,即延迟了必须达到30min才能执行消费的逻辑】

5.kafka 如何做到单机上百万的高吞吐量?

答案

高吞吐量的含义

在计算机领域,吞吐量是指系统在单位时间内处理请求传输数据 的能力 。高吞吐量意味着系统能在相同时间内处理更多的请求、传输更多的数据。对于 Kafka 来说,单机上达到百万级的高吞吐量,就是指在一台机器上,Kafka 每秒能高效处理百万数量级 的消息写入和读取操作。

写入数据快的原因

  • 磁盘顺序写
    • 原理 :传统磁盘随机写时,磁头需要频繁移动来定位不同的存储位置 ,寻道时间长,严重影响写入速度。而 Kafka 将消息追加到分区日志文件末尾 ,属于顺序写操作。顺序写不需要磁头频繁移动,大大减少了寻道时间,提高了写入效率。
    • 对比:与传统数据库的随机写相比,数据库中数据更新、插入位置不确定,会导致磁头频繁移动,写入性能受限。例如,关系型数据库在插入数据时,可能需要更新索引、数据页等,涉及大量随机 I/O 操作,而 Kafka 的顺序写方式在写入速度上具有明显优势。
  • 页面缓存
    • 原理 :操作系统会将磁盘上的数据块缓存到内存的页面缓存 中。Kafka 写入数据时,先写入页面缓存,并不立即刷入磁盘。后续如果有读取操作,直接从页面缓存读取,减少磁盘 I/O。而且,当页面缓存中的数据积累到一定量或者满足特定条件时,才会批量刷入磁盘,批量操作也提升了整体写入效率。
    • 对比:如果没有页面缓存,每次写入都直接操作磁盘,会产生大量的小 I/O 请求,效率低下。像一些不使用页面缓存的简单文件存储系统,每次写入都要等待磁盘操作完成,性能远不如 Kafka。

读取数据快的原因

  • 原理 :传统的数据读取过程中,数据会在用户空间和内核空间多次拷贝 ,例如从磁盘读取数据到内核缓冲区,再从内核缓冲区拷贝到用户缓冲区,这会消耗大量的 CPU 和内存资源。而零拷贝技术(如 Linux 中的 sendfile 函数)允许数据直接在内核空间完成传输,跳过用户空间,减少了数据拷贝次数,大大提升了数据传输效率,从而实现高读取吞吐量。
  • 对比:一些不支持零拷贝技术的系统,在读取数据时由于多次数据拷贝,会占用较多的 CPU 和内存资源,导致读取性能下降。例如,在一些早期的网络文件传输系统中,数据需要在多个缓冲区之间拷贝,传输效率远低于使用零拷贝技术的 Kafka。
相关推荐
七夜zippoe2 小时前
分布式事务性能优化:从故障现场到方案落地的实战手记(二)
java·分布式·性能优化
栀椩2 小时前
springboot配置请求日志
java·spring boot·后端
番薯大佬2 小时前
Python学习-day8 元组tuple
java·python·学习
何似在人间5752 小时前
Go语言快速入门教程(JAVA转go)——1 概述
java·开发语言·golang
疯子@1232 小时前
nacos1.3.2 ARM 版容器镜像制作
java·linux·docker·容器
庄小焱3 小时前
大数据存储域——Kafka设计原理
大数据·kafka·消息中间件
Swift社区3 小时前
如何解决 Spring Bean 循环依赖
java·后端·spring
我真的是大笨蛋3 小时前
从源码和设计模式深挖AQS(AbstractQueuedSynchronizer)
java·jvm·设计模式
空山新雨(大队长)3 小时前
Java第五课:输入输出
java·开发语言