Linux C/C++ 学习日记(73):Kafka(一):基本介绍

注:该文用于个人学习记录和知识交流,如有不足,欢迎指点。

一、消息队列

消息+队列(MessageQueue,简称MQ)。

本质是就是个队列,FIFO先入先出,只不过队列中存放的内容是message,从而叫消

息队列。

主要用途:不同服务server、进程process、线程thread之间通信。

简单点讲:生产者跟消费者之间加了一个消息队列缓冲,有了这个生产者只需要讲消息推送到消息队列里面,消费者通过订阅来主动拉取消息队列里面的消息。

使用消息队列的好处:

  • 解耦:允许我们独立的扩展或修改队列两边的处理过程。
  • 可恢复性:即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。
  • 缓冲:有助于解决生产消息和消费消息的处理速度不一致的情况。
  • 灵活性&峰值处理能力:不会因为突发的超负荷的请求而完全崩溃,消息队列能够使关键组件顶住
  • 突发的访问压力。
  • 异步通信:消息队列允许用户把消息放入队列但不立即处理它

二、Kafka

本质上是一个 MQ(Message Queue)

  • Producer:消息生产者,向 Kafka Broker 发消息的客户端。
  • Consumer:消息消费者,从 Kafka Broker 取消息的客户端。
  • Consumer Group:消费者组(CG),消费者组内每个消费者负责消费不同分区的数据,提高消费能力。一个分区只能由组内一个消费者消费,消费者组之间互不影响。所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。
  • Broker:一台 Kafka 机器就是一个 Broker。一个集群(kafka C1uster)由多个 Broker 组成。一个 Broker 可以容纳多个 topic。
  • topic:可以理解为一个队列,topic 将消息分类,生产者和消费者面向的是同一个 topic。
  • Partition:为了实现扩展性,提高并发能力,一个非常大的 topic 可以分布到多个 Broker (即服务器)上,一个 topic 可以分为多个 Partition,同一个topic在不同的分区的数据是不重复的,每个 Partition 是一个有序的队列,其表现形式就是一个一个的文件夹。
  • Replication:每一个分区都有多个副本,副本的作用是做备胎。当主分区(Leader)故障的时候会选择一个备胎(Follower)上位,成为Leader。在kafka中默认副本的最大数量是10个,且副本的数量不能大于Broker的数量,follower和leader绝对是在不同的机器,同一机器对同一个分区也只可能存放一个副本(包括自己)。
  • Message:消息,每一条发送的消息主体。
  • Leader:每个分区多个副本的"主"副本,生产者发送数据的对象,以及消费者消费数据的对象,都是 Leader。
  • Follower:每个分区多个副本的"从"副本,实时从 Leader 中同步数据,保持和 Leader 数据的同步。Leader 发生故障时,某个 Follower 还会成为新的 Leader。
  • Offset:消费者消费的位置信息,监控数据消费到什么位置,当消费者挂掉再重新恢复的时候,可以从消费位置继续消费。(不同的Partition的offset 是独立的。)
  • ZooKeeper:Kafka 集群能够正常工作,需要依赖于 ZooKeeper,ZooKeeper 帮助 Kafka 存储和管理集群信息。

三、消息存储

  • Kafka 中消息是以 topic 进行分类的,生产者生产消息,消费者消费消息,面向的都是同一个 topic。
  • topic 是逻辑上的概念,而 Partition 是物理上的概念,每个 Partition 对应于一个 log 文件,该 log 文件中存储的就是 Producer 生产的数据。
  • Producer 生产的数据会不断追加到该 log 文件末端,且每条数据都有自己的 Offset。
  • 消费者组中的每个消费者,都会实时记录自己消费到了哪个 Offset,以便出错恢复时,从上次的位置继续消费。
  • 日志默认在:/tmp/kafka-logs

四、副本原理

副本机制(Replication),也可以称之为备份机制,通常是指分布式系统在多台网络互联的机器上保

存有相同的数据拷贝。副本机制的好处在于:

  1. 提供数据冗余。在一部分节点宕机的时候,系统仍能继续工作(即提高可用性)。
  2. 提供高伸缩性。支持扩大机器数量,从而可以支撑更高的读请求量,比如fastdfs、mongodb。
  3. 改善数据局部性。允许将数据放入与用户地理位置相近的地方,从而降低系统延时。

目前Kafka只实现了副本机制带来的第 1 个好处,即是提供数据冗余实现高可用性和高持久性。

基于领导者(Leader-based)的副本机制

  1. 在 Kafka 中,副本分成两类:领导者副本(Leader Replica)和追随者副本(FollowerReplica)。每个分区在创建时都要选举一个副本,称为领导者副本,其余的副本自动称为追随者副本。
  2. Kafka 副本机制中的追随者副本是不对外提供服务的,不同于Fastdfs、MongdoDB等。
  3. 当领导者副本挂掉了,或领导者副本所在的 Broker 宕机时,Kafka 依托于 ZooKeeper 提供的监控功能能够实时感知到,并立即开启新一轮的领导者选举,从追随者副本中选一个作为新的领导者。老 Leader 副本重启回来后,只能作为追随者副本加入到集群中。

五、分区

1. 分区和主题的关系

  1. 一个分区只能属于一个主题
  2. 一个主题可以有多个分区
  3. 同一主题的不同分区内容不一样,每个分区有自己独立的offset
  4. 同一主题不同的分区能够被放置到不同节点的broker
  5. 分区规则设置得当可以使得同一主题的消息均匀落在不同的分区

2. 分区的作用:

主要提供负载均衡的能力,能够实现系统的高伸缩性(Scalability)。不同的分区能够被放置到不同节点的机器上,而数据的读写操作也都是针对分区这个粒度而进行的,这样每个节点的机都能独立地执行各自分区的读写请求处理。这样,当性能不足的时候可以通过添加新的节点机器来增加整体系统的吞吐量。

分区原则:我们需要将 Producer 发送的数据封装成一个 ProducerRecord对象。

该对象需要指定一些参数:

topic:string 类型,NotNull。

partition:int 类型,可选。

timestamp:long 类型,可选。

key:string 类型,可选。

value:string 类型,可选。

headers:array 类型,Nullable。

六、生产者

producer就是生产者,是数据的入口。Producer在写入数据的时候永远的找leader,不会直接将数据写入follower。

分区策略(生产者端:消息发往哪个分区)

1. 轮询策略(默认)

  • 解释:按顺序将消息均匀分配到每个分区。
  • 例子:若有 3 个分区,消息 1→分区 0,消息 2→分区 1,消息 3→分区 2,消息 4→分区 0,循环往复。

2. 按 Key 分区

  • 解释:根据消息的 Key 哈希值取模分区数,相同 Key 的消息始终进入同一分区。
  • 例子:Key="user123" 的消息永远发往分区 2,保证该用户消息有序。

3. 自定义分区

  • 解释:通过实现 Partitioner 接口,根据业务逻辑(如地域、优先级)指定分区。
  • 例子:将 "VIP" 标签的消息优先发往分区 0,普通消息发往其他分区

4. 默认分区规则

  • 指明 Partition 的情况下,直接将给定的 Value 作为 Partition 的值。
  • 没有指明 Partition 但有 Key 的情况下,将 Key 的 Hash 值与分区数取余得到 Partition 值。
  • 既没有 Partition 有没有 Key 的情况下,第一次调用时随机生成一个整数(后面每次调用都在这个整数上自增),将这个值与可用的分区数取余,得到 Partition 值,也就是常说的 Round-Robin轮询算法。

七、消费者

1. 传统消息引擎

传统消息引擎的两大核心模型均存在伸缩性局限:

  • 点对点队列模型:消息消费即删、仅支持单消费者消费,本质是其原生特性而非缺陷;但多消费者场景下需争抢共享队列消息,伸缩性极差。
  • 发布 / 订阅模型:虽支持多消费者消费,但要求订阅者全量订阅主题所有分区,灵活性与消息投递效率受限,同样存在伸缩性短板。

2. Kafka 的 Consumer Group 机制

Kafka 的 Consumer Group 机制结合 Broker 端消息留存能力,彻底解决了上述问题:消费组订阅多主题后,组内消费者仅需消费主题的部分分区,无需全量订阅;不同消费组间彼此独立,可订阅相同主题而互不干涉。

仅通过 Consumer Group 单一机制,Kafka 即可同时兼容传统消息引擎的两大核心模型:

  • 所有消费者归属同一消费组,即实现点对点队列模型;
  • 所有消费者分属不同消费组,即实现发布 / 订阅模型。

3. 消费方式

Consumer 采用 Pull(拉取)模式从 Broker 中读取数据。

Pull 模式则可以根据 Consumer 的消费能力以适当的速率消费消息。Pull 模式不足之处是,如果 Kafka没有数据,消费者可能会陷入循环中,一直返回空数据。

因为消费者从 Broker 主动拉取数据,需要维护一个长轮询,针对这一点, Kafka 的消费者在消费数据时会传入一个时长参数 timeout。

如果当前没有数据可供消费,Consumer 会等待一段时间之后再返回,这段时长即为 timeout。

注意:

一个消费者可以订阅多个主题,可以去消费多个分区,一个分区不支持多个消费者(同一个消费组)读取。

一个消费者组中有多个 consumer,一个 topic 有多个 partition,所以必然会涉及到 partition 的分配问题,即确定那个 partition 由哪个 consumer 来消费。当消费者组里面的消费者个数发生改变的时候,也会触发再平衡。

Kafka 有四种分配策略,可以通过参数 partition.assignment.strategy 来配置,默认 Range +

CooperativeSticky。

4. 分配策略(消费者端:分区分给哪个消费者)

Kafka 的分区分配策略,全称是消费组内分区分配策略,核心是解决「消费组内,哪个消费者有权消费哪个主题的哪个分区」的问题,是 Consumer Group 实现负载均衡、高可用的核心机制。

所有分配策略必须严格遵守 3 条铁则:

  1. 分区独占铁则 :同一个主题的同一个分区,只能被同一个消费组内的一个消费者消费(避免同组重复消费),但一个消费者可消费多个分区。
  2. 订阅权限铁则 :某主题的分区,只能分配给订阅了该主题的消费者,绝不会分给同组内未订阅该主题的消费者(完美适配「同组内不同消费者订阅主题可不同」的场景)。
  3. 重平衡触发铁则 :分配不是一次性的,出现以下场景会触发重平衡(Rebalance,即分区再分配):消费组成员变更(消费者宕机 / 新增 / 退出)、订阅关系变更、主题分区数变更。

固定场景:消费组 G 有 3 个消费者 C0、C1、C2

  • C0 订阅主题 A、主题 B

  • C1 订阅主题 A、主题 B

  • C2 仅订阅主题 B

  • 主题 A 有 3 个分区:A0、A1、A2;

    主题 B 有 4 个分区:B0、B1、B2、B3

4.1 RangeAssignor(Kafka 0.x-2.3 版本默认策略)

4.1.1 核心逻辑

单个主题为独立单位 ,先将主题的分区按序号排序,再将订阅了该主题的消费者按名称排序,给每个消费者分配连续的分区范围。计算规则:对单个主题,分区数 P,订阅该主题的消费者数 C,基础分配数base=P/C,余数rem=P%C,前rem个消费者各多分 1 个分区(base+1),剩余消费者分base个。

4.1.2 同组异订阅处理

完全按主题独立计算,每个主题仅纳入订阅了它的消费者,未订阅的消费者不参与该主题的分配。

4.1.3 宕机重平衡表现

重平衡时会全量重新计算所有主题的分区分配,哪怕和宕机消费者无关的分区,也可能被重新分配,分区迁移量极大,重平衡开销高。

实例演示
  • 正常分配结果:

    1. 主题 A:仅 C0、C1 订阅
      C0:A0、A1;
      C1:A2
    2. 主题 B:3 个消费者均订阅,
      C0:B0、B1;
      C1:B2
      C2:B3
    3. 最终:
      C0 :A0,A1,B0,B1
      C1 :A2,B2
      C2: B3
  • C0 宕机后重分配结果:剩余消费者 C1、C2,全量重算:

    1. 主题 A:仅 C1 订阅 → 全部分给 C1(A0、A1、A2)
    2. 主题 B:2 个消费者订阅,
      C1:B0、B1
      C2:B2、B3
    3. 最终:
      C1 :A0,A1,A2,B0,B1
      C2 :B2,B3

4.2 RoundRobinAssignor(轮询分配策略)

4.2.1 核心逻辑

将消费组内所有主题的全部分区按「主题名 - 分区号」字典序全局排序,再按消费者名称排序轮询分配;若当前消费者未订阅该分区的主题,直接跳过,分给下一个符合订阅要求的消费者。

4.2.2 同组异订阅处理

全局轮询时严格校验订阅权限,仅将分区分给订阅了对应主题的消费者,实现跨主题的全局均匀分配。

4.2.3 宕机重平衡表现

分配均匀性优于 Range,但重平衡仍会全局重新轮询全部分区,非必要的分区迁移量依然较高。

实例演示
  • 正常分配结果:

    全部分区排序:A0、A1、A2、B0、B1、B2、B3;

    消费者排序:C0、C1、C2

    轮询分配后:A0→C0、A1→C1、A2→C0、B0→C1、B1→C2、B2→C0、B3→C1

    最终:

    C0 :A0,A2,B2

    C1 :A1,B0,B3

    C2 :B1(均匀性远优于 Range)

  • C0 宕机后重分配结果:剩余消费者 C1、C2,全局重算轮询,

    最终:

    C1:A0,A1,A2,B0,B2

    C2 :B1,B3


4.3 StickyAssignor(粘性分配策略,Kafka 0.11.x 引入)

4.3.1 核心逻辑

设计有两大优先级目标:

  1. 首要目标:保证分区分配的全局均匀性:
  2. 核心目标:重平衡时,尽可能保留消费者原持有的分区(粘性),仅迁移必须变动的分区,最大限度减少分区迁移量
4.3.2 遵循的原则:
初始分配:无历史约束下的「全局最优」

当消费组第一次启动,或所有消费者都重新加入时,没有任何历史分区持有记录,StickyAssignor 的目标是建立一个最合理的初始状态,优先级如下:

  1. **单主题内严格均匀(硬约束)**这是不可突破的底线,必须保证每个主题的分区在其订阅者之间尽可能平均,差值不超过 1。
  2. 全局负载均衡在满足单主题均匀的前提下,调整分配,让所有消费者的总分区数尽可能接近。
  3. 最大化后续重平衡的粘性潜力选择那些在未来成员变动时,能让分区迁移量最小的分配方案,为后续重平衡打基础。

在这个例子中:初始分配B主题均匀分配给了三个消费者,遵循的是但主题严格均匀。(虽然C3多拿一个B会让全局更均衡)

重平衡:有历史约束下的「最小变动」

当消费组发生成员变更(如消费者宕机、新增、退出)时,已经存在历史分区持有情况,StickyAssignor 的核心目标变成了最小化分区迁移,保证业务连续性,优先级彻底反转:

  1. **100% 保留存活消费者的原有分区(绝对优先)**这是 StickyAssignor 的灵魂,存活消费者之前持有的分区,一个都不能被夺走,必须完全保留,避免不必要的消费中断。
  2. 仅迁移宕机 / 退出消费者的分区只处理那些 "无主" 的分区,绝不触碰存活消费者的 "地盘"。
  3. 全局负载均衡在迁移宕机分区时,优先分给当前负载更低的存活消费者,让全局负载尽可能接近。
  4. **单主题均衡(最后考虑)**只有在满足前三个条件后,才会考虑单主题的均匀性,必要时可以牺牲单主题均衡来保证粘性和全局负载。

在这个例子中,重平衡时 C1 保留了 B3、C2 保留了 B1,就是因为这是它们的 "原有分区",绝对不能动;然后把 C0 的 B0、B2 分给负载更低的 C2,导致主题 B 的分配看起来不均衡,但这是为了遵守 "保留原有分区" 的硬约束。

阶段 核心目标 优先级顺序 关键约束
初始分配 建立最合理的初始状态 单主题均匀 → 全局均衡 → 粘性潜力 无历史分区持有记录
重平衡 最小化分区迁移,保证业务连续 保留原有分区 → 迁移宕机分区 → 全局均衡 → 单主题均衡 有历史分区持有记录
4.3.3 同组异订阅处理

先严格校验订阅权限,再保证均匀性,最后保障粘性,完美适配同组消费者订阅不同主题的场景。

4.3.4 宕机重平衡表现

这是它的核心优势:消费者宕机后,存活消费者原持有的分区 100% 保留不变,仅将宕机消费者持有的分区,重新分配给符合订阅要求的存活消费者,彻底杜绝非必要的分区迁移,重平衡开销大幅降低。

实例演示
  • 正常分配结果(优先保证均匀):
    C0 :A0,B0,B2
    C1 :A1,A2,B3
    C2 :B1

    问: C2为什么不多拿一个B2这样分配不是更均衡吗?
    答:Kafka优先遵循单主题均衡,即三个消费者都订阅了B,那么尽可能将B均衡分配之后(至少各拿一个分区)。再考虑全主题均衡

  • C0 宕机后重分配结果:

    1. 存活消费者原有分区完全保留:C1 仍持有 [A1,A2,B3],C2 仍持有 [B1]
    2. 仅迁移宕机消费者 C0 的分区 [A0,B0,B2]:A0 仅 C1 订阅,分给 C1;B0、B2 分给负载更低的 C2
      最终:
      C1 :A0,A1,A2,B3
      C2 :B0,B1,B2
      可见:仅迁移了 C0 的 3 个分区,存活消费者原有分区无任何变动,分区迁移量降到了理论最低值。

4.4 CooperativeStickyAssignor(合作式粘性分配策略,Kafka 2.4+ 新版默认策略)

4.4.1 核心逻辑

在 Sticky 粘性分配的基础上,彻底解决了前 3 种策略的Stop The World(全量停服) 问题:将「全量重平衡」升级为增量式合作重平衡。重平衡时,不需要所有消费者停止消费、重新加入消费组,仅需要调整待迁移的分区,存活消费者可以继续消费自己原持有的分区,全程无全量停服,同时完全继承了 Sticky 的低迁移量优势。

4.4.2 同组异订阅处理

和 Sticky 完全一致,严格适配同组消费者订阅不同主题的场景。

4.4.3 宕机重平衡表现

消费者宕机后,存活消费者全程不停止原有分区的消费,仅对宕机消费者的分区进行增量再分配,分配完成后仅通知对应消费者新增分区的消费,业务几乎无感知,是目前生产环境的最优选择。


4.5 四种策略核心对比表

分配策略 核心优势 核心缺陷 同组异订阅适配 宕机重平衡迁移量 适用场景
RangeAssignor 实现简单,兼容老版本 分配易不均,重平衡迁移量极大 适配,按主题独立计算 极高 老版本 Kafka 兼容场景
RoundRobinAssignor 跨主题全局分配均匀 重平衡仍全量重算,非必要迁移多 适配,全局轮询校验权限 同组消费者订阅主题完全一致的场景
StickyAssignor 分配均匀,重平衡迁移量极低 重平衡仍需全量 Stop The World 完美适配 极低(仅迁移宕机分区) 2.4 版本之前的生产环境
CooperativeStickyAssignor 迁移量最低,无全量停服,业务无感知 仅支持 2.4 + 新版本 完美适配 理论最低值 新版本 Kafka 生产环境首选

4.6 关键注意事项

  1. 同一个消费组内,所有消费者必须配置相同的分配策略,否则会触发策略冲突,导致重平衡失败、消费异常。
  2. 分区数建议设置为消费组内消费者数的整数倍,能最大限度保证分配均匀性。
  3. 重平衡不仅会触发分区再分配,还会提交消费位移,不合理的分配策略会加剧重复消费、消费延迟等问题。

八、数据可靠性保证

为保证 Producer 发送的数据,能可靠地发送到指定的 topic,topic 的每个 Partition 收到 Producer 发送的数据后,都需要向 Producer 发送 ACK(ACKnowledge 确认收到)。如果 Producer 收到 ACK,就会进行下一轮的发送,否则重新发送数据。

1. 副本数据同步策略

|-----------------|--------------------------------|----------------------------------|
| 方案 | 缺点 | 优点 |
| 半数以上完成同步,就发送ack | 延迟低 | 选举新的leader时,容忍n台节点故 障,需要2n+1个副本。 |
| 全部完成同步, 才发送ack | 选举新的leader时,容忍n台节点故障,需要n+1个副本。 | 延迟高。 |

当采用第二种方案时,所有 Follower 完成同步,Producer 才能继续发送数据,设想有一个 Follower 因为某种原因出现故障,那 Leader 就要一直等到它完成同步。这个问题怎么解决?

  1. Leader维护了一个动态的** in-sync replica set(ISR)**:和 Leader 保持同步的 Follower 集

合。

  1. 当 ISR 集合中的 Follower 完成数据的同步之后,Leader 就会给 Follower 发送 ACK。

  2. 如果 Follower 长时间未向 Leader 同步数据,则该 Follower 将被踢出 ISR 集合,该时间阈值由

replica.lag.t1me.max.ms 参数设定。Leader 发生故障后,就会从 ISR 中选举出新的 Leader。

2. ACK 应答机制

对于某些不太重要的数据,对数据的可靠性要求不是很高,能够容忍数据的少量丢失,所以没必要等ISR 中的 Follower 全部接受成功。

所以 Kafka 为用户提供了三种可靠性级别,用户根据可靠性和延迟的要求进行权衡,选择以下的配置。

ACK 参数配置:

0:Producer 不等待 Broker 的 ACK,这提供了最低延迟,Broker 一收到数据还没有写入磁盘就

已经返回,当 Broker 故障时有可能丢失数据。

1:Producer 等待 Broker 的 ACK,Partition 的 Leader 落盘成功后返回 ACK,如果在 Follower

同步成功之前 Leader 故障,那么将会丢失数据。

-1(all):Producer 等待 Broker 的 ACK,Partition 的 Leader 和 Follower 全部落盘成功后才返

回 ACK。但是在 Broker 发送 ACK 时,Leader 发生故障,则会造成数据重复。

3. 可靠性指标

软件系统的可靠性只能够无限去接近100%,但不可能达到100%。kafka保障可靠性的措施

  • 分区副本,你可以创建更多的分区来提升可靠性,但是分区数过多也会带来性能上的开销,一般来说,3个副本就能满足对大部分场景的可靠性要求
  • ACKS,生产者发送消息的可靠性,也就是我要保证我这个消息一定是到了broker并且完成了多副本的持久化。参考2.5.2 AC应答机制保障消息到了broker之后,消费者也需要有一定的保证,因为消费者也可能出现某些问题导致消息没有消费到。
  • enable.aut0.C0mmit默认为true,也就是自动提交offset,自动提交是批量执行的,有一个时间窗口,这种方式会带来重复提交或者消息丢失的问题,所以对于高可靠性要求的程序,要使用手动提交。 对于高可靠要求的应用来说,宁愿重复消费也不应该因为消费异常而导致消息丢失。
相关推荐
王夏奇2 小时前
python-PyQt6库学习
开发语言·python·学习
圆弧YH2 小时前
排版基本操作
学习
啊阿狸不会拉杆2 小时前
《计算机视觉:模型、学习和推理》第 19 章-时序模型
人工智能·python·学习·机器学习·计算机视觉·时序模型
Errorbot2 小时前
GPS学习(二)使用树莓派5和GPS PPS 实现微秒级精度的时间同步
学习·ubuntu·gps
Moshow郑锴2 小时前
Spark与Prophecy综合比较&&推荐Prophecy的理由
大数据·分布式·spark
嘉琪0012 小时前
Day1 完整学习包(var/let/const + 作用域)——2026 0310
前端·javascript·学习
·中年程序渣·2 小时前
Spring AI Alibaba入门学习(三)
java·学习·spring
xx24062 小时前
RN学习笔记
笔记·学习
Agno ni2 小时前
RAG-anthing学习笔记
笔记·学习