cswork / 系统设计

第 28 章 / 共 57 章 · 约 5 分钟

关键技术:Kafka

本页目录
  1. 1. 核心模型
  2. 2. 什么时候用
  3. 3. 什么时候不适合
  4. 4. 关键限制和数量级
  5. 5. 面试中常被追问的点
  6. 6. 和其他技术的取舍
  7. 7. 本站案例
  8. 章节测验

Kafka 是一个分布式的、只追加的日志。生产者把消息写到日志末尾,消费者按自己的进度往后读,消息读完不会被删掉,保留期内可以反复重放。系统设计里只要出现“把事件可靠地交给多个下游”“削峰”“事后重算”,Kafka 基本都是第一候选。

1. 核心模型

  • Topic 和分区(partition):一个 topic 拆成多个分区,每个分区是一段有序、不可修改的日志,每条消息有一个递增的 offset。
  • 按 key 分区:生产者对消息 key 做哈希来选分区,同一个 key 的消息总在同一个分区里,因此顺序只在分区内成立,跨分区没有全局顺序。
  • 复制:每个分区有一个 leader 和若干 follower,跟得上 leader 的副本组成 ISR(in-sync replicas)。读写都走 leader。
  • 消费者组(consumer group):同一组里,一个分区同一时刻只分给一个消费者。组内消费者数超过分区数,多出来的会闲着,所以分区数就是一个组的并行度上限。不同的组互不影响,各自记自己的 offset。
  • 保留:按时间或大小删除旧数据(常见默认 7 天);也可以开日志压缩(compaction),每个 key 只保留最新一条,适合当作“最新状态表”。
Kafka 分区与消费者组
滚动到此处后加载图表…
图表可横向滚动,展开后可缩放查看。
查看图表源码
flowchart LR
  accTitle: Kafka 分区与消费者组
  p["生产者:按 key 哈希选分区"] --> p0["分区 0"]
  p --> p1["分区 1"]
  p --> p2["分区 2"]
  p0 --> a1["消费者组 A:消费者 1"]
  p1 --> a1
  p2 --> a2["消费者组 A:消费者 2"]
  p0 --> b1["消费者组 B:一个消费者读全部分区"]
  p1 --> b1
  p2 --> b1

2. 什么时候用

  • 事件分发:订单创建后,库存、通知、搜索索引、数据仓库各自消费同一份事件,互不拖累。
  • 削峰填谷:上游突发流量先写进 Kafka,下游按自己的速度消费,不会被打垮。
  • 可重放:下游有 bug,修好后把 offset 拨回去重新处理;新上线的服务可以从头回填历史数据。
  • 流处理的输入输出:点击日志、位置更新这类高吞吐数据流,交给 Flink 之类的引擎做实时聚合。
  • 变更数据捕获(CDC):把数据库的 binlog/WAL 变更写进 Kafka,用来同步缓存、搜索索引。

3. 什么时候不适合

  • 简单的任务队列:需要逐条确认、可见性超时、延迟投递、优先级的场景,SQS、RabbitMQ 这类队列更顺手。Kafka 只按 offset 前进,单条消息处理失败不好“只重试这一条”。
  • 请求/响应:同步调用就用 RPC,绕一圈 Kafka 只会增加延迟和复杂度。
  • 大消息:单条消息默认上限约 1 MB。图片、视频放对象存储,Kafka 里只传对象的 key。
  • 很小的规模:每秒几十条消息、一个消费者,用数据库表或 Redis 列表就够了,不值得运维一个集群。

4. 关键限制和数量级

维度要记住的事
吞吐单分区大约每秒几 MB 到几十 MB;单个 broker 每秒几百 MB;集群可以到每秒百万条以上
分区数按“目标吞吐 ÷ 单分区吞吐”和“消费者并行度”取较大值,再留余量。分区可以加不能减,而且加分区会改变 key 到分区的映射
持久性副本数 3、acks=all、min.insync.replicas=2:已确认的写入在一台 broker 挂掉时不丢。acks=1 在 leader 挂掉时可能丢已确认的数据
语义默认“至少一次”:处理完再提交 offset,崩溃后会重复处理。Kafka 事务能做到 Kafka 内部的读-处理-写恰好一次,但写外部数据库、发邮件这类副作用仍要靠消费者幂等
延迟端到端通常是几毫秒到几十毫秒,批量和压缩会拿延迟换吞吐
元数据新版本用内置的 Raft 元数据集群(KRaft),4.0 起不再依赖 ZooKeeper

5. 面试中常被追问的点

  • 怎么保证同一个用户的消息有序? 用 user_id 当 key,同一用户落在同一分区;消费者内部也要按分区串行处理,不能把一个分区的消息丢进线程池乱序执行。
  • 消费者处理到一半崩溃怎么办? 重启后从上次提交的 offset 继续,会重复处理一部分,所以下游写入要幂等:用消息里的业务 id 做唯一键,或者记录已处理的 id。
  • 一条消息反复失败(毒消息)怎么办? 重试几次后转到重试 topic 或死信 topic,不要卡住整个分区。
  • 热点分区:某个 key 流量特别大,会压垮一个分区。可以给 key 加随机后缀打散,代价是这个 key 不再整体有序,下游需要再合并。
  • 消费者跟不上:看 consumer lag;扩消费者不能超过分区数,所以分区要一开始就留够。
  • 重放会不会把下游打爆:回放时限速,或者先写到新的表里再切换。

6. 和其他技术的取舍

选项适合代价
Kafka高吞吐事件流、多个下游、需要重放运维重;分区和顺序要提前设计
SQS / RabbitMQ任务队列:逐条确认、超时重投、延迟、死信消息消费后就没了,不能重放给新下游
Kinesis托管的类 Kafka 流,按 shard 计费单 shard 写入约 1 MB/s 或每秒 1000 条,读取约 2 MB/s
Redis Streams / Pub/Sub轻量场景、低延迟受内存限制;Pub/Sub 不持久,没人订阅时消息直接丢

面试时一句话的选择理由:需要多个下游各自消费、能重放、吞吐高,就用 Kafka;只是把任务交给一组 worker,就用队列。

7. 本站案例

章节测验

5 道选择题
  1. 一个 topic 有 6 个分区,同一个消费者组里启动了 10 个消费者,会发生什么?
  2. 聊天系统要求同一个会话里的消息按发送顺序被处理,Kafka 该怎么设计?
  3. 消费者采用“处理完再提交 offset”,写数据库后、提交 offset 前崩溃。重启后会怎样,应该怎么应对?
  4. 以下哪种情况最不适合用 Kafka?
  5. 副本数为 3 的 topic,怎样配置才能在一台 broker 宕机时不丢失已确认的写入?

章节讨论

正在加载讨论…