消息队列深入学习(1) kafka

本章将以卡夫卡为核心深入学习消息队列的方方面面

必要性

分布式

这是宏观上消息队列非常大的优势, 其所带来的好处可以细分如下 :

  • 解耦 :

    消息队列专注于生产消费模型, 其作为中间层可以很好地将这两方面解耦, 像我们在代码中使用queue + 条件变量的组合其实本质就是为了实现该功能.

  • 独立 / 持久化 :

    消息队列一般运行在一个独立的服务器上, 这样就算消费者崩溃, 消息队列也可以持续作为中间层接收生产者的消息. 并且持久化到本地, 也可以同时供多个消费者读取同一批消息.

  • 异步 :

    生产者不必阻塞等待消费者完成消费动作, 并且不同的消费者可以通过分区同时消费消息.

  • 负载均衡 :

    和nginx的理念差不多, 多个消费者可以由消息队列进行负载均衡, 提升读取效率.

削峰填谷

这是功能上最核心也最明显的优势, 本质就是可以当一个独立的buffer, 由于消息产生的速率在不同时间段都是不同的, 在消息高峰, 当消费者处理速度跟不上时, 就可以预存在这里, 当消息低谷时, 消费者处理速度跟上时, 就可以慢慢从消息队列这里把多的取出来消费了.

Kafka

topic(消息分类机制)

这是一个逻辑层面上的机制, 你可以认为一个topic就对应一个队列, 生产者可以通过指定topic发送消息, 消费者指定topic取消息.

  • 为什么要设计topic?

    本质原因就是只搞一个队列不够用, 并且对于队列的读写在底层肯定是要加锁的, 如果只对一个队列进行大量读写, 并行度肯定高不了, 这样分多个topic的优势就显而易见了.

partition(物理分区)

分区, 每个partition可以被认为是kafka中最小的物理单元.

topic只是逻辑上的队列, 每个topic中都会有很多个partition, 每个partiton内部存储不同的消息且有序, partition之间消息并非有序, 全看发来消息时负载均衡后存在哪里.

一个topic下的逻辑结构类似如下 :

1
2
3
4
Topic: xxx
├── Partition 0: [消息A, 消息D, 消息G, ...]
├── Partition 1: [消息B, 消息E, 消息H, ...]
└── Partition 2: [消息C, 消息F, 消息I, ...]
  • 为什么要分多个partition?

    因为这是为了配合负载均衡的机制, 当多个消费者读取消息时, 可以同时读取不同的分区, 以提高并发量.

一个partition的物理结构类似如下 :

1
2
3
4
5
6
7
xxx-0/
├── 00000000000000000000.log // 第一个日志段文件
├── 00000000000000000000.index // 对应的偏移量索引文件
├── 00000000000000000000.timeindex // 时间戳索引文件
├── 00000000000000000001.log // 第二个日志段文件
├── 00000000000000000001.index
└── ...

看到partiton也许会有很多疑问, 我们一个一个解释 :

  • log :

    实际数据存储单元, 从前往后会存储多个log文件. 需要分成多个文件的原因如下 :

    • 配合索引, 存在不同文件中可以利用索引快速查找.
    • 配合持久化机制, 在删除过期消息时可以从前往后根据过期时间一个一个删除.
  • index :

    kafka使用一种叫”稀疏索引”的方式进行查找目标消息, 这里不深入.

  • 为什么需要查找?

    本质是kafka有持久化机制, 并非消息被一个消费者读取后就要删除, kafka中的所有消息在过期或专门删除之前都会长期保存. 这是为了让一个消息可以供多个消费者一起读取. (这很好理解, 一个消息出现, 可能具体的处理者要读, 可能数据分析服务也要读)

    每个消费者内部都有自己当前的读取进度, 因此需要查找到自己要读的下一个消息.

消费者组

你可以认为处理相同事务的消费者应当放到一个消费者组中, 这样kafka在内部负载均衡后会将目标topic中的每个partition分配均匀的消费者, 例子如下 :

1
2
3
4
5
Topic: my-topic (3个Partition: P0, P1, P2)
Group: my-group
├── Consumer-1 → P0
├── Consumer-2 → P1
└── Consumer-3 → P2
  • 每个消费者都会维护自己读取分区的进度.

  • 一个partition在同一个消费者组内只能被一个消费者读取.

  • 不同消费者组之间的读取进度相互独立, 互不影响. 例子如下 :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    Topic: user-actions (3个Partition)
    // 组A:实时数据分析
    Group: analytics-group
    ├── Consumer-A1 → P0
    ├── Consumer-A2 → P1
    └── Consumer-A3 → P2
    // 组B:数据归档存储
    Group: archive-group
    ├── Consumer-B1 → P0
    ├── Consumer-B2 → P1
    ├── Consumer-B3 → P2
    └── Consumer-B4 → 无
    // 组C:监控告警
    Group: monitoring-group
    ├── Consumer-C1 → P0, p1
    └── Consumer-C2 → P2
  • 当一个消费者组中有消费者下线, 会触发重平衡机制, 重新分配每个消费者要读的partition.

broker(高扩展性 / 高可用)

一个Broker代表Kafka集群中的一台服务器, 你可以认为topic/partition属于整个系统层面的概念, 而broker是针对物理层面上给出的一个逻辑概念, 用来维护这个系统在多个服务器上的协调工作.

broker的逻辑结构类似如下 :

1
2
3
4
5
/ 每个Broker负责存储一部分Partition数据
Broker 0:
├── Topic: orders, Partition 0
├── Topic: payments, Partition 1
└── Topic: logs, Partition 0

物理结构类似如下 :

1
2
3
4
5
6
7
8
9
# Broker磁盘上的数据组织
/tmp/kafka-logs/
├── topic-orders-0/ # orders topic, partition 0
│ ├── 00000000000000000000.log # 数据文件
│ ├── 00000000000000000000.index # 索引文件
│ └── 00000000000000000000.timeindex
├── topic-payments-1/ # payments topic, partition 1
│ ├── 00000000000000000000.log
│ └── 00000000000000000000.index
  • 高扩展性 :

    如果所有partition都在一个服务器上运行, 那么单机压力会比较高, 所以可能需要多开几个服务器, 每个作为一个broker, 每个broker负责几个的partition, 就可以分摊压力实现高扩展性.

  • 高可用 :

    和redis差不多, 就是保证一个broker挂了之后topic内部的partition还在, 本质就是做主从复制(replicas).

    一个topic中对于broker的管理类似如下 :

    1
    2
    3
    4
    5
    // 一个3副本的Partition示例
    Topic: important-data, Partition 0:
    ├── Leader副本: Broker 1 (处理所有读写请求)
    ├── Follower副本: Broker 2 (从Leader同步数据)
    └── Follower副本: Broker 3 (从Leader同步数据)
  • Controller :

    这是一个特殊的Broker, 整个系统有且仅有1个, 其主要任务就是管理和决策, 其目的可以认为是将每个broker和topic/partition联系起来, 其职责不限于 :

    • 监视每个partition的leader, 当leader崩溃时决定由哪个follower接任.
    • 如果有新topic的partition加入, 分配给合适的broker.
    • 如果有新broker加入, 重新分配每个broker负责的分区.
    • 等等

    没必要深入理解, 知道有就行.

持久化保留策略

这在上面已经间接提过一些了, 主要就是两点 :

  • 过期策略 : 在partition中创建新log时可以设置过期时间, 过期后系统内部会自动删去.
  • 大小策略 : 可以对partition的大小设置限制, 当超过总大小后会从前往后依次删除.

ZK保活机制

kafka中有很多的组件, 这些组件大多都是维护在不同的服务器上, 所以为了保证整个系统互通有无, 需要zookeeper作为一个中枢站, 持续保存所有节点的存活状态, 其他节点会监控(心跳)相关节点的加入与删除来触发某些机制.

和zk相关的机制如下 :

  • Broker的注册与发现, 当broker数量变动会触发分区leader的重新选举.
  • Controller崩溃后重新选举出新Controller.
  • 消费者组重平衡, 当消费者组中数目变化时也会之间触发重平衡.
  • 等等

不过使用zk是早期版本的行为, 毕竟kafka要使用的只是zk的简单功能, 来保活自己的系统. 现在已经完全移除了对zk的依赖, 被称为KRaft模式, 该模式用raft协议发送心跳和内置一些选举机制来替代zk的功能.


消息队列深入学习(1) kafka
http://example.com/2025/11/30/消息队列深入学习(1) kafka/
作者
天目中云
发布于
2025年11月30日
许可协议