Voocii博客
首页博客AI 热榜作品集读书友链工具关于
© 2026 Voocii. Built with Next.js & tRPC.
GitHubXEmailRSS


Kafka 简介:概念、特性、项目实践与选型对比

技术RickRick2025年10月7日·5 次阅读

本文从核心概念讲起,梳理 Kafka 的特性与典型应用场景,再用一个电商订单系统串起架构、消息流和业务逻辑的实践,最后简单对比 RabbitMQ、RocketMQ、Pulsar 等常见方案。

一、Kafka 是什么

Apache Kafka 最初由 LinkedIn 开发,后来捐给 Apache 基金会。它常被叫做“消息队列”,但更准确的定位是:分布式事件流平台(Distributed Event Streaming Platform)。

它做三件事:

  1. 发布与订阅事件流(类似消息系统);
  2. 持久化存储事件流,并且可以按需重复读取;
  3. 实时处理事件流(Kafka Streams、Flink 等)。

理解 Kafka 的关键,是把它想成一个分布式的、只追加写入的提交日志(Commit Log)。消息写进去之后不会因为被消费就消失,而是按保留策略(时间或大小)保存,消费者只是记录“我读到哪了”。这一点是它与传统消息队列最本质的区别。

二、核心概念

2.1 基本角色

概念说明
Producer生产者,向 Kafka 发送消息
Consumer消费者,从 Kafka 拉取消息
BrokerKafka 服务节点,多个 Broker 组成集群
Topic主题,消息的逻辑分类,如 order-created
Partition分区,Topic 的物理分片,是并行度和有序性的基本单位
Offset分区内每条消息的唯一递增序号
Consumer Group消费者组,组内成员共同分担一个 Topic 的分区
Replica副本,分区在多个 Broker 上的拷贝,用于高可用

2.2 Topic 与 Partition

一个 Topic 被切成若干 Partition,每个 Partition 是一个独立的、有序的、只追加的日志文件:

Topic: order-events

Partition 0:  [0][1][2][3][4][5] →
Partition 1:  [0][1][2][3] →
Partition 2:  [0][1][2][3][4] →

几个要点:

  • 有序性只在分区内保证,Topic 级别没有全局顺序;
  • 同一个 Key 的消息经过哈希会进入同一个分区,因此“同一订单的事件有序”可以通过用订单 ID 作 Key 实现;
  • 分区数决定了一个消费者组内最大并行消费者数量。

2.3 消费者组(Consumer Group)

  • 同一个组内,一个分区只会被一个消费者消费;
  • 不同组之间互不影响,各自维护自己的 Offset。

所以 Kafka 同时支持两种模式:组内竞争消费(类似队列)和组间广播(类似发布订阅)。比如“库存服务”和“数据分析服务”各用一个消费者组,就都能完整收到订单事件。

2.4 副本与高可用

每个分区有一个 Leader 和若干 Follower。读写都走 Leader,Follower 异步(准确说是拉取)同步数据。与 Leader 保持同步的副本集合叫 ISR(In-Sync Replicas)。

关键参数组合:

  • replication.factor=3:每个分区 3 个副本;
  • min.insync.replicas=2:至少 2 个副本确认才算写入成功;
  • 生产者 acks=all:等待 ISR 全部确认。

三者配合,可以在单个 Broker 宕机时既不丢数据,又不中断服务。

2.5 集群元数据:从 ZooKeeper 到 KRaft

早期 Kafka 依赖 ZooKeeper 管理集群元数据。新版本引入了基于 Raft 的 KRaft 模式,由 Kafka 自己管理元数据;从 4.0 开始已经完全移除了 ZooKeeper。新项目直接使用 KRaft 即可,部署和运维都更简单。

三、核心特性

3.1 高吞吐、低延迟

Kafka 单集群可以轻松达到每秒百万级消息。主要得益于:

  • 顺序写磁盘:追加写入,避免随机 IO;
  • 页缓存(Page Cache):依赖操作系统缓存,而不是自己维护堆内缓存;
  • 零拷贝(Zero-copy):通过 sendfile 直接把磁盘数据送到网卡;
  • 批量与压缩:生产者按批发送,支持 lz4、zstd、snappy 等压缩;
  • 分区并行:横向扩展靠增加分区和 Broker。

3.2 持久化与可重放

消息按保留策略落盘,消费完不会删除。这带来两个很实用的能力:

  • 重放(Replay):把 Offset 重置回去,就可以重新处理历史数据,用来修 Bug、回填数据、接入新系统;
  • 日志压缩(Log Compaction):对相同 Key 只保留最新值,适合做状态快照类 Topic。

3.3 可扩展与容错

分区可以分布在不同 Broker 上,加机器即可扩容;消费者组在成员变化时会触发 Rebalance,自动重新分配分区。

3.4 灵活的投递语义

  • At most once:最多一次,可能丢;
  • At least once:至少一次,可能重复(最常用的默认选择);
  • Exactly once:通过幂等生产者(enable.idempotence=true)+ 事务实现,主要在“Kafka 读 → 处理 → Kafka 写”的链路内成立。

需要注意:Exactly once 只在 Kafka 内部闭环成立,一旦涉及外部系统(数据库、HTTP 调用),仍然需要业务层做幂等。这一点后面实践部分会展开。

3.5 生态

  • Kafka Connect:与数据库、Elasticsearch、S3 等系统做数据集成;
  • Kafka Streams / ksqlDB:轻量级流处理;
  • Schema Registry:管理 Avro / Protobuf / JSON Schema,保证消息格式演进兼容;
  • 与 Flink、Spark 等计算引擎深度集成。

四、典型应用场景

  1. 异步解耦:下单后,库存、积分、通知等下游各自订阅,不再同步调用;
  2. 日志与埋点收集:应用日志、用户行为数据统一进入 Kafka,再分发给 ELK、数据仓库;
  3. 事件驱动架构 / 事件溯源:把“发生了什么”作为一等公民存下来;
  4. 实时数据管道:数据库变更通过 CDC(如 Debezium)进入 Kafka,同步到搜索、缓存、数仓;
  5. 流式计算:实时风控、实时大屏、实时推荐;
  6. 削峰填谷:流量洪峰时先写入 Kafka,后端按自己的节奏消费。

五、实际项目中的实践

下面以一个电商订单系统为例,说明在真实项目里怎么用 Kafka。示例做了简化,但思路是通用的。

5.1 整体架构

                          ┌───────────────┐
   用户请求 ─→ 订单服务 ──┤  Kafka 集群   ├──→ 库存服务(组:inventory)
               │          │               ├──→ 支付服务(组:payment)
               ▼          │ order-created ├──→ 通知服务(组:notification)
            订单数据库     │ order-paid    ├──→ 数据分析(组:analytics)
          (含 Outbox 表)   │ order-dlq     │
               │          └───────────────┘
               └── Outbox 转发器(轮询 / CDC) ──→ Kafka

设计思路:

  • 订单服务只负责创建订单并发布事件,不直接调用下游;
  • 每个下游服务使用独立的消费者组,互不影响,也方便独立扩缩容;
  • 订单事件用订单 ID 作为 Key,保证同一订单的事件落在同一分区,顺序可控;
  • 失败消息进入死信 Topic(DLQ),不阻塞主流程。

5.2 Topic 与消息设计

Topic 命名:领域.实体.事件,例如 order.order.created、order.order.paid。团队统一规范,后期治理会轻松很多。

消息结构(以 JSON 为例,生产环境推荐 Avro / Protobuf + Schema Registry):

{
  "eventId": "7c1f2a9e-5b34-4a8e-9a77-0e1c1b2d3f40",
  "eventType": "OrderCreated",
  "occurredAt": "2026-10-26T08:30:00Z",
  "version": 1,
  "data": {
    "orderId": "ORD-20261026-0001",
    "userId": "U10086",
    "items": [{ "sku": "SKU-001", "qty": 2 }],
    "amount": 199.00
  }
}

几个经验:

  • 一定带 eventId(用于消费端去重)和 version(用于格式演进);
  • 事件描述“已经发生的事实”(OrderCreated),而不是命令(CreateOrder),这样下游耦合更低;
  • 消息体不要过大,大文件放对象存储,消息里只放引用。

分区数:按“目标吞吐 ÷ 单分区吞吐”估算,并为未来增长留余量。分区只能增加不能减少,且增加分区会改变 Key 到分区的映射,所以初期要规划好。

5.3 消息流

完整的一次下单流转:

  1. 用户下单,订单服务在同一个数据库事务中写入订单表和 Outbox 表;
  2. Outbox 转发器把事件发送到 order.order.created;
  3. 库存服务消费,扣减库存,成功后发布 inventory.stock.reserved;
  4. 支付服务收到库存预留事件,发起支付;支付完成后发布 order.order.paid;
  5. 通知服务、数据分析服务订阅 order.order.paid,分别发短信和写入报表;
  6. 任一环节失败,发布补偿事件(如 inventory.stock.released),订单服务据此更新状态。

这种“事件接力 + 补偿”的方式,本质上是 Saga 模式(编排式 / 协同式),用来替代跨服务的分布式事务。

5.4 业务逻辑中的关键问题

问题一:数据库与 Kafka 如何保持一致(Outbox 模式)

最常见的坑:先写数据库再发 Kafka,如果中间进程挂了,数据库有订单而消息没发出去;反过来则是消息发了但订单回滚。

解决方案是 Transactional Outbox:

  • 业务数据和“待发送事件”在同一个本地事务里写入;
  • 另有转发器读取 Outbox 表发送到 Kafka,发送成功后标记已发送;
  • 转发器可以是定时轮询,也可以用 Debezium 通过 CDC 读取 binlog。

这样得到的是 至少一次 的投递,重复问题交给消费端处理。

问题二:消费端幂等

既然是至少一次,消费者就必须能容忍重复。常用手段:

  • 用 eventId 建去重表(唯一索引),先插入再处理,插入失败说明已处理过;
  • 业务本身天然幂等,比如“把订单状态置为已支付”,重复执行结果一致;
  • 带版本号的乐观更新。

问题三:生产者配置

以 .NET 的 Confluent.Kafka 为例:

var config = new ProducerConfig
{
    BootstrapServers = "kafka1:9092,kafka2:9092,kafka3:9092",
    Acks = Acks.All,                  // 等待 ISR 全部确认
    EnableIdempotence = true,         // 幂等生产者,避免重试导致重复
    CompressionType = CompressionType.Lz4,
    LingerMs = 5,                     // 少量等待,换取更好的批量效果
    MessageTimeoutMs = 30000
};

using var producer = new ProducerBuilder<string, string>(config).Build();

var result = await producer.ProduceAsync("order.order.created",
    new Message<string, string>
    {
        Key = order.Id,               // 同一订单进入同一分区
        Value = JsonSerializer.Serialize(evt)
    });

问题四:消费者的 Offset 提交与重试

核心原则:处理成功之后再提交 Offset,也就是关闭自动提交,手动提交。

var config = new ConsumerConfig
{
    BootstrapServers = "kafka1:9092,kafka2:9092,kafka3:9092",
    GroupId = "inventory",
    EnableAutoCommit = false,
    AutoOffsetReset = AutoOffsetReset.Earliest
};

using var consumer = new ConsumerBuilder<string, string>(config).Build();
consumer.Subscribe("order.order.created");

while (!ct.IsCancellationRequested)
{
    var cr = consumer.Consume(ct);
    try
    {
        await handler.HandleAsync(cr.Message);   // 内部包含幂等检查
        consumer.Commit(cr);                     // 成功后再提交
    }
    catch (TransientException)
    {
        // 可重试错误:本地有限次重试 / 退避
    }
    catch (Exception ex)
    {
        await dlqProducer.ProduceAsync("order.order.created.dlq", cr.Message);
        consumer.Commit(cr);                     // 进入死信后继续往前走
    }
}

Node.js 项目里可以用 kafkajs 或 @confluentinc/kafka-javascript,思路完全一致。

重试策略建议分层:

  1. 进程内短重试(网络抖动、临时锁冲突);
  2. 重试 Topic(.retry-5s、.retry-1m)做延迟重试,避免阻塞分区;
  3. 超过次数进入 DLQ,配合告警和人工处理工具。

问题五:消息顺序

  • 需要顺序的消息,必须用相同 Key;
  • 生产者要开启幂等,并控制 max.in.flight.requests.per.connection(开启幂等时 ≤ 5 仍能保证有序);
  • 消费端同一分区内必须串行处理,不能随意丢到线程池里并发乱序执行;
  • 一旦采用“失败后发到重试 Topic”,该条消息就会脱离原顺序,需要业务上评估是否可接受。

问题六:Rebalance 与消费积压

  • 处理时间过长会超过 max.poll.interval.ms,被踢出组引发 Rebalance;
  • 优先使用协作式粘性分配(CooperativeStickyAssignor),减少“全组停摆”;
  • 监控 Consumer Lag(Burrow、Kafka UI、Prometheus + kafka-exporter),它是最重要的健康指标;
  • 积压时先判断是消费者变慢还是生产突增,再决定扩容分区 / 消费者,还是优化处理逻辑。

5.5 运维与治理清单

  • 监控:Broker 磁盘与网络、Under-replicated Partitions、Consumer Lag、请求延迟;
  • 安全:TLS 加密 + SASL 认证 + ACL 授权;
  • 数据保留:根据业务设定 retention.ms,需要长期保存的走归档(S3 + Connect);
  • Schema 管理:启用兼容性检查(向后兼容优先),禁止随意改字段含义;
  • 环境隔离:开发 / 测试 / 生产各自的集群或严格的前缀规范;
  • 云托管:团队没有专职运维时,可考虑 Confluent Cloud、AWS MSK 等托管服务。

5.6 几个常见坑

  1. 以为 Kafka 提供 Topic 全局有序,实际只有分区内有序;
  2. 分区数设置过少,后期扩容破坏 Key 的映射;
  3. 开启自动提交,进程崩溃时出现“消息丢了但已提交”;
  4. 消费逻辑没有幂等,重试一次就多扣一次库存;
  5. 把 Kafka 当 RPC 用,同步等待回复,失去解耦意义;
  6. 单条消息过大(默认上限约 1MB),拖垮 Broker 和网络。

六、与其他消息框架的简单对比

维度KafkaRabbitMQRocketMQPulsar
定位分布式事件流平台传统消息代理业务消息中间件云原生流 / 消息平台
吞吐量极高中等高高
消息模型分区日志,拉取交换机 + 队列,推送为主Topic + Queue,拉取为主Topic + 订阅,多种订阅模式
消息保留按时间 / 大小保留,可重放消费后删除可保留,可重放可保留,分层存储
顺序性分区内有序单队列有序队列内有序分区内有序
延迟 / 定时消息原生支持弱,需自行设计插件 / 死信队列实现原生支持较好原生支持
事务消息事务(Kafka 内部闭环)较弱原生事务消息支持事务
路由灵活性较弱,主要靠 Topic 与 Key很强,多种 Exchange中等(Tag 过滤)中等
运维复杂度中等(KRaft 后降低)较低中等较高(计算存储分离,组件多)
生态最丰富(Connect、Streams)成熟,协议多样国内生态较好持续成长

怎么选

  • 日志采集、数据管道、流计算、事件溯源、超高吞吐 → 首选 Kafka;
  • 复杂路由、任务队列、RPC 式异步、低到中等流量、需要优先级队列 → RabbitMQ 更顺手;
  • 订单、支付等强业务场景,需要延迟消息、事务消息、消息过滤 → RocketMQ 很合适;
  • 多租户、跨地域复制、需要存储与计算分离、统一队列与流 → 可以评估 Pulsar;
  • 轻量级场景:Redis Stream、NATS 也值得了解,它们在简单场景下部署成本更低。

没有“最好”的消息中间件,只有“最适合当前场景”的。如果团队规模小、流量不大,只是想做个简单的异步任务队列,RabbitMQ 或云厂商的队列服务往往比自建 Kafka 更省心;一旦出现“多个系统要消费同一份数据”“需要回放历史”“数据量很大”这些信号,Kafka 的价值才会真正体现。

七、总结

  • Kafka 的本质是分布式提交日志,消息持久化、可重放,消费者自己管理 Offset;
  • 分区是并行度与顺序性的基本单位,Key 的设计直接决定顺序与负载均衡;
  • 可靠性靠 副本 + acks + min.insync.replicas 保障,而端到端的正确性靠 Outbox + 幂等消费 + DLQ 这套业务层组合拳;
  • 选型时看场景:吞吐与数据管道选 Kafka,复杂路由与业务消息可以考虑 RabbitMQ、RocketMQ 等。

后续可以继续了解:Kafka 存储与索引机制、Rebalance 协议细节、Kafka Streams 与 Flink 的流处理实战、基于 Debezium 的 CDC 实践。

评论 (0)

暂无评论,快来抢沙发吧!

发表评论

目录
  • 一、Kafka 是什么
  • 二、核心概念
  • 三、核心特性
  • 四、典型应用场景
  • 五、实际项目中的实践
  • 六、与其他消息框架的简单对比
  • 七、总结