Kafka学习笔记(五):面试高频问题总结

学完 Kafka 的基本操作之后,面试里最容易遇到的情况是:名词都认识,但面试官再追问一句“为什么”,回答就接不上了。

例如,“Kafka 如何保证消息不丢?”如果只回答 acks=all,接下来就可能被问到:只有一个副本怎么办?消费者提前提交进度怎么办?订单已经落库但事件没发出去怎么办?

这篇把前四篇整理成一份面试复习笔记。每个问题先给一段简短回答,再补充追问要点。复习时先练习把主干讲清楚,再用故障场景解释机制。

**版本约定:**沿用本系列的 Kafka 4.0、KRaft 学习基线;消费组部分讨论 KafkaConsumer.subscribe() 使用的常规消费组。提到 classic 与新 consumer 协议时会单独区分。配置默认值与行为应按实际客户端和 Broker 版本核对。

系列导航:

  1. 核心概念与架构
  2. Docker 与 Java 入门实战
  3. 消费组、Offset 与 Rebalance
  4. 消息可靠性、幂等与事务
  5. 面试高频问题总结

本文目录:


一、基础概念与架构

1. Kafka 是什么?主要解决哪些问题?

简短回答:

Kafka 是一个分布式事件流平台,核心能力是写入、保存和读取事件。它通过分区实现并行,通过副本提高容错能力,通过消费组让多个业务独立读取同一份事件流,常用于异步处理、服务解耦、流量缓冲和数据管道。

追问要点:

  • 订单创建后,通知与统计可以使用不同消费组独立读取事件。
  • 流量缓冲需要足够的存储与保留时间;长期消费速度低于生产速度,积压仍会增长。
  • 引入 Kafka 后,还需要处理重复、重放、事件兼容性和最终一致性。

2. Kafka 的架构由哪些角色组成?还需要 ZooKeeper 吗?

简短回答:

Producer 写事件,Consumer 读事件,Broker 保存分区日志并处理读写请求,Controller 管理集群元数据并协调分区状态。Kafka 4.0 已移除 ZooKeeper 模式,使用 KRaft 管理元数据。

追问要点:

  • Controller 的元数据共识,与业务分区的 Leader/Follower 复制是两层机制。
  • 本地实验可让一个进程同时担任 Broker 和 Controller;生产部署需要结合故障域和容量设计。
  • 回答旧系统时先确认版本,不要把 ZooKeeper 时代的架构套到 Kafka 4.0。官方升级说明明确了这个边界。

3. Topic、Partition、Replica 有什么区别?

简短回答:

Topic 是一类事件的逻辑集合,Partition 是其中的一条有序日志,Replica 是同一分区日志的副本。分区主要解决并行,副本主要解决故障冗余。

追问要点:

1
2
3
3 个分区 × 每个分区 3 个副本 = 9 份分区副本

一个消费组分配的仍然是 3 个逻辑分区。

增加副本不会直接增加同组消费者的可用并行度。副本还应该分散到不同节点,必要时跨机架或可用区,避免同一故障同时影响多份数据。

4. Offset 是什么?能当作消息的全局唯一 ID 吗?

简短回答:

Offset 是记录在某个分区中的逻辑位置,只在该分区内有意义。定位一条 Kafka 记录,需要 Topic、Partition、Offset 的组合;识别一次业务事件,通常还需要独立的 eventId。

追问要点:

  • P0 的 Offset 10 与 P1 的 Offset 10 是不同记录。
  • Offset 不保证连续,日志压缩、事务等可能造成可见记录之间的间隙。
  • 同一次业务事件被重新发送后,可能获得新的 Offset,因此业务去重不能只依赖它。

5. 消费完消息后,Kafka 会删除它吗?delete 与 compact 有什么区别?

简短回答:

消费进度与消息清理是独立的。提交 Offset 不会删除消息;delete 根据时间、大小等保留规则清理旧日志段,compact 则按 Key 清理旧值,使日志适合恢复某个 Key 的最新状态。

追问要点:

策略 更适合表达什么 容易答错的地方
delete 保留一定窗口内的事件历史 未消费的旧数据也可能被清理
compact 按 Key 恢复当前状态 清理是后台进行的,不会立即只剩一条

compact 也不是“订单号相同就不再写入”。消费者在清理发生前仍可能读到多个版本。分段存储可参考日志实现说明,压缩规则见Log Compaction 设计。


二、生产者与性能

6. 一条消息从 send() 到 Broker,大致经过哪些步骤?

简短回答:

生产者获取元数据,序列化 Key 和 Value,确定分区,将记录放入对应分区的批次缓冲区,再由后台发送线程发给分区 Leader,最后根据确认策略和发送结果完成 Future 或回调。

追问要点:

1
序列化 → 选择分区 → 批次缓冲 → 后台发送 → 确认或失败

send() 通常是异步的,但获取元数据或等待缓冲空间时也可能阻塞。调用返回不等于 Broker 已确认;应用需要检查 Future 或回调中的最终结果。KafkaProducer API说明了这些行为。

7. Kafka 如何选择分区?相同 Key 一定落到同一分区吗?

简短回答:

显式指定分区时使用指定值;否则按分区器与配置选择。有 Key 时,默认策略通常根据 Key 选择分区。在分区数、Key 序列化与分区策略稳定时,相同 Key 会进入同一分区。

追问要点:

  • 无 Key 时不能一概回答“严格轮询”,默认策略与客户端版本、配置有关。
  • 自定义分区器、忽略 Key 的配置,以及不同的序列化方式都可能改变映射。
  • 扩分区可能使同一 Key 的新旧事件分布到不同分区,对严格顺序业务要设计迁移。

8. Kafka 为什么能实现较高吞吐?

简短回答:

Kafka 通过日志顺序追加、操作系统页缓存、批量传输、批次压缩和分区并行来分摊读写开销。在适用的网络路径下,还可以通过零拷贝减少数据复制。

追问要点:

  • 零拷贝不代表从磁盘到网卡完全没有复制,也不能无条件套到所有加密与传输路径。
  • Kafka 会持久化日志,不能回答成“全部存在内存里所以快”。
  • 消息大小、压缩率、ACK、副本复制、磁盘和 Key 热点都会影响性能。官方效率设计可用于进一步展开。

9. batch.size 与 linger.ms 分别控制什么?如何取舍?

简短回答:

batch.size 控制生产者按分区组批时的批次大小目标,linger.ms 控制等待更多记录加入批次的时间。适当组批能提高吞吐,但可能增加等待延迟,需要按消息大小和延迟目标测量。

追问要点:

  • batch.size 不是整个 Producer 的内存上限,也不是所有分区共用一个批次。
  • 不应说“只有填满批次才发送”;批次可因等待时间等条件发送。
  • 确认策略、背压和网络状态也会影响实际延迟;linger.ms 不是端到端延迟上限。生产者配置列出了这些参数。

三、消息可靠性与一致性

10. acks=0、1、all 有什么区别?

简短回答:

0 不等待 Broker 确认,1 等待 Leader 本地日志写入确认,all 等待当前 ISR 的复制确认。更强的确认策略通常提高可靠性要求,但实际保障还依赖副本、ISR 和故障范围。

追问要点:

配置 典型问题
0 应用无法据此知道 Broker 是否接收
1 Leader 失效时,尚未复制的记录可能丢失
all 如果同步副本只剩一个,冗余仍可能不足

“本地日志写入”也不能直接等同于每条消息都已经完成 fsync。ACK 配置说明是回答这个问题的依据。

11. acks=all 配合 min.insync.replicas=2,是等任意两个副本吗?

简短回答:

不是。min.insync.replicas 设置最低同步副本要求,acks=all 仍等待当前 ISR 所需的复制确认。ISR 有三个时,不是只要其中任意两个完成就返回;ISR 缩到两个时,可以在满足条件后继续写;只剩一个时无法成功确认。

追问要点:

假设副本数为 3:

1
2
3
ISR = A、B、C → 按当前三个同步副本完成复制
ISR = A、B → 达到最低门槛,可继续写入
ISR = A → 低于门槛,写入不能成功确认

这是本系列使用的经典 ISR 配置例子。副本不足时拒绝写入,是可靠性与可用性的取舍;应用必须保留失败事件并恢复发送。最低 ISR 配置解释了该门槛。

12. ISR 是什么?Leader 宕机后怎么恢复?

简短回答:

ISR 是保持同步的分区副本集合,包含 Leader。落后或失效的副本可能被移出 ISR。Leader 失效后,Controller 协调新 Leader 的选举与元数据更新,客户端更新元数据后继续访问。

追问要点:

  • 在未启用 ELR 的经典机制中,干净选举从 ISR 中选择符合条件的副本。
  • 如果没有合适副本,允许落后副本接任的非干净选举可能提高可用性,但会有数据丢失风险。
  • Kafka 4.0 还引入了 ELR(Eligible Leader Replicas)机制,讨论具体候选集合时要确认是否启用,不能把所有版本都概括成“只能从当前 ISR 选”。ELR 官方说明介绍了这一扩展。

13. Kafka 生产者幂等如何实现?能解决所有重复吗?

简短回答:

幂等生产者使用生产者身份和分区序列号,让 Broker 识别客户端协议级重试造成的重复写入。它解决特定发送链路中的重复,不能把应用主动发送两次的业务事件自动合并,也不能避免消费者重复执行业务。

追问要点:

  • 开启幂等需满足 acks=all、retries>0、max.in.flight.requests.per.connection<=5。
  • 同一个 Key、Value 主动调用两次 send(),仍然是两次发送。
  • 跨应用重试仍需要稳定的 eventId 与恢复策略。幂等配置约束可用于核对参数。

14. 如何保证消息不丢?

简短回答:

我会沿生产、存储、消费三段分析:生产端检查发送结果并可恢复重试;存储端配置合理的副本、ACK 和最低 ISR;消费端在业务持久化成功后提交进度。数据库到事件发送的双写窗口,还要用 Outbox 或其他恢复方案处理。

追问要点:

不能做无条件的“不丢”承诺,要把故障假设讲清楚:

  • 发送超时可能是确认丢了,并不一定是消息没到。
  • 多份副本同时永久丢失,不属于普通单节点容错能兜底的范围。
  • 消费落后超过保留窗口,所需记录可能已经清理。
  • 代码捕获发送或处理异常后直接忽略,配置再强也不能补回应用主动丢弃的事件。

15. 为什么会重复消费?如何实现消费幂等?

简短回答:

业务已经提交,但 Offset 未提交时进程崩溃,恢复后会再次处理。可以使用稳定 eventId,通过数据库唯一约束记录消费身份,并把去重记录与业务更新放进同一个本地事务,事务成功后再提交消费进度。

追问要点:

1
2
3
去重记录 + 业务更新:同一个数据库事务
↓
提交 Kafka Offset

唯一键可使用 (consumer_name, event_id),让不同业务各自去重。只使用 Redis 锁或先写 SETNX 标记,无法自动保证标记与业务结果一起成功;外部调用则需要下游支持幂等请求键等机制。

16. Kafka 能实现 Exactly-Once 吗?事务包含什么?

简短回答:

对 Kafka 到 Kafka 的处理,可以把输出记录与输入消费组进度放进同一个 Kafka 事务,并让读取方使用 read_committed,实现这个处理边界内的 Exactly-Once 语义。它不会自动把 MySQL、短信或第三方 HTTP 调用纳入同一个原子提交。

追问要点:

  • 事务生产者配置 transactional.id,初始化后开始、提交或中止事务。
  • 输入消费者关闭自动提交,用 sendOffsetsToTransaction() 提交输入进度。
  • 中止事务不会自动回退消费者的内存 Position,需要重新定位或重建客户端恢复。
  • 不能只回答“开启生产者幂等就实现端到端 Exactly-Once”。事务 API 见KafkaProducer 文档。

17. 订单写入 MySQL 成功,Kafka 发送失败,怎么办?

简短回答:

用 Outbox 将订单和待发送事件写进同一个数据库事务。事务提交后,由投递器或 CDC 把事件发送到 Kafka。发送失败可以恢复;发送成功但投递状态未更新时可能重复,所以要复用 eventId 并让消费端幂等。

追问要点:

1
2
3
4
5
MySQL 事务:订单 + Outbox
↓
可恢复的异步投递
↓
Kafka

本地数据库的 @Transactional 不会自动消除 Kafka 与 MySQL 的双写窗口。投递器还要处理并发抢占、错误重试、清理与同一业务实体的事件顺序。CDC 的一种实现可参考Debezium Outbox Event Router。


四、消费者与进度管理

18. Kafka 消费者是 Pull 还是 Push?会一直空轮询吗?

简短回答:

Kafka 使用拉取模型。客户端主动发起 Fetch 请求并通过 poll() 向应用返回记录;Broker 可以等待数据或达到等待时间后返回,减少没有数据时的忙轮询。

追问要点:

  • 拉取方便消费者按自身能力控制处理节奏,也便于批量获取。
  • fetch.min.bytes 与 fetch.max.wait.ms 影响聚合与等待的取舍。
  • poll() 不只是简单的一次网络读取,还涉及客户端缓存、消费组协调等行为。

19. 消费组如何实现负载均衡?消费者越多越快吗?

简短回答:

常规消费组把订阅分区分配给组内成员,稳定状态下同一分区由组内一个成员负责,一个成员可以负责多个分区。并行度受订阅分区数限制,还受 Key 热点与下游能力影响。

追问要点:

一个组只订阅 3 个分区的 Topic,启动 5 个消费者,至少有两个分不到分区。不同业务想各读完整事件流,应使用不同组,而不是把通知和统计混在一个组里争抢分区。

20. Position 与 Committed Offset 有什么区别?提交的是哪一条?

简短回答:

Position 是客户端下一次读取的位置,随 poll() 返回记录而推进;Committed Offset 是持久化的恢复位置。提交的是下一待处理位置,不是“最后一条成功记录的编号”本身。

追问要点:

1
2
处理完成:Offset 5、6、7
应该提交:8

消息进入线程池,不代表业务成功,不能直接按 Position 提交。Kafka 4.0 的 ConsumerRecords.nextOffsets() 可在本批对应分区全部处理完成后提供下一位置。KafkaConsumer 的位置说明区分了这两个概念。

21. auto.offset.reset=earliest,为什么没有从头消费?

简短回答:

它只在没有有效初始提交位置,或位置已经越出可读范围等情况下起作用。已有有效提交位置时,消费者按该位置继续。earliest 指当前仍保留的最早位置,也不保证是 0。

追问要点:

场景 结果
保留 50~99,已提交 80 从 80 继续
保留 50~99,新组配置 earliest 从 50 开始
保留 50~99,提交位置已过期 按重置策略恢复

重放应使用明确的进度重置或独立消费组,提前处理副作用。配置触发条件是这个问题的关键。

22. 自动提交、commitSync()、commitAsync() 怎么选?

简短回答:

自动提交按客户端机制周期推进进度;手动提交让应用控制提交时机。commitSync() 等待结果,失败以异常体现;commitAsync() 不等待完成,通过回调观察结果。选择时要考虑业务完成边界、延迟和失败恢复。

追问要点:

  • 自动提交不是必然丢消息,关键是每次轮询返回的记录是否在后续轮询或关闭前完成处理。
  • 手动提交也不自动安全,不能吞掉处理异常后提交整批进度。
  • 异步提交失败时,不应盲目重试一个过期的低 Offset;新进度可能已提交,需要设计单调推进与失败恢复。
  • 同步提交本身也可能失败,不能写完一行 commitSync() 就忽略恢复路径。

23. 什么是 Rebalance?什么时候发生?

简短回答:

Rebalance 是消费组重新分配分区工作。成员加入、退出、失效或订阅分区集合变化都可能触发。分区交接时,新负责人从有效提交进度恢复,因此可能重复处理旧负责人已执行但未提交的记录。

追问要点:

  • 不要概括成“每次都让所有成员撤销全部分区”,具体影响取决于协议与分配方式。
  • Rebalance 回调可以配合提交、清理和停止分区任务,但业务仍需幂等。
  • Kafka 4.0 的新协议可通过 group.protocol=consumer 使用,分配与心跳配置的管理方式与 classic 不同。官方协议说明介绍了区别。

24. session.timeout.ms 与 max.poll.interval.ms 有什么区别?

简短回答:

在 classic 协议下,前者用于组协调器判断成员心跳是否失联,后者限制应用两次调用 poll() 的最大间隔。后台还有心跳,不意味着业务处理可以无限阻塞轮询。

追问要点:

例如每条同步处理 1 秒,一轮 500 条可能耗时约 500 秒。排查时应测量慢调用,减小本轮处理量或改进处理机制,再判断超时配置,而不是只把所有超时调大。

新 consumer 协议的心跳间隔与会话超时由对应 Broker 参数管理,不能直接照搬 classic 的配置。具体规则见消费者配置。

25. KafkaConsumer 线程安全吗?能把消息交给线程池吗?

简短回答:

KafkaConsumer 不能由多个线程同时操作,wakeup() 是用于跨线程通知的例外。可以让消费线程负责客户端、工作线程处理业务,但必须维护每个分区的连续完成进度,并处理分区撤销与旧任务停止。

追问要点:

1
2
P0:100 成功,101 失败,102 成功
不能因为 102 成功,就直接提交 103。

线程池还可能打乱同分区业务执行顺序。严格有序时可以按分区或业务 Key 串行执行,并限制任务队列、施加背压。线程模型与关闭方式见官方消费者 API。


五、顺序性与工程排查

26. Kafka 如何保证消息有序?

简短回答:

Kafka 提供分区内日志顺序,跨分区没有统一全局顺序。业务有序需要生产端按正确顺序发送,把同一业务实体路由到同一分区,控制发送重试的顺序,并让消费端按相应顺序完成业务。

追问要点:

  • orderId 可以用作订单事件的 Key,但不能修复源头已颠倒的发送顺序。
  • 开启幂等且满足约束,可保持对应重试场景下的分区顺序;不必一律把 max.in.flight 改为 1。
  • 线程池、独立重试 Topic、扩分区都可能改变业务执行顺序。
  • 单分区能给日志一个共同顺序,但不能自动定义多个生产者之间的业务先后关系,还会限制并行。

27. 出现消息积压,你会怎么排查?

简短回答:

先按分区看 Lag、消费速率和业务处理耗时,判断是总能力不足、Key 热点、下游变慢,还是异常与 Rebalance 让进度停住,再针对原因优化。只有增加消费者也能增加有效并行时,扩实例才有帮助。

追问要点:

现象 优先检查
所有分区一起落后 总消费能力、下游吞吐、资源限制
单个分区严重落后 热点 Key、异常事件、局部慢调用
进度反复停住 处理和提交失败、频繁 Rebalance
Lag 较低但业务慢 内存任务队列、下游排队、提交过早

Lag 通常是日志末尾位置与提交位置的差值,并不总等于业务消息的精确条数。追赶时间可粗略估为“积压量 /(消费速率 - 新增速率)”,要求两种速率可比且消费快于新增。监控指标可参考官方监控文档。

28. 一条消息一直处理失败,怎么重试?怎么处理死信?

简短回答:

先区分短暂故障与无法自动恢复的错误。短暂故障做有界重试;无法自动恢复的事件记录上下文并转入错误处理链路。无论重试还是隔离,都不能让输入进度提前越过尚未妥善处理的记录。

追问要点:

  • Kafka 的普通消费者 API 不会根据应用异常自动完成整套业务重试与死信处理,需要应用或框架实现。
  • 错误 Topic 应保留 eventId、原 Topic、Partition、Offset 和失败原因。
  • 写入错误 Topic 与提交输入进度也有双写窗口,可用 Kafka 事务结合,或设计确认、去重与补偿。
  • 严格顺序业务不能直接把失败事件移走、让后续事件继续,而忽略状态依赖。

29. 分区数怎么定?扩分区有什么影响?

简短回答:

根据目标吞吐、实测单分区处理能力、消费实例需求和集群开销确定,再预留增长空间。分区越多不一定越快;扩分区会增加并行机会,也可能改变 Key 映射,影响严格顺序和热点分布。

追问要点:

  • 用真实消息大小、ACK、压缩与业务逻辑压测,不能引用一个固定“每分区吞吐”套所有系统。
  • 多分区会增加文件、内存、复制和协调成本。
  • 扩分区不会自动把已有历史记录均匀搬到新分区。
  • 原地增加分区通常可以做,直接减少已有 Topic 分区数不支持;需要新 Topic 与迁移设计。Topic 操作说明介绍了分区调整。

30. 面试官让你介绍项目里怎么使用 Kafka,如何组织回答?

简短回答框架:

先介绍业务触发与事件,再说明 Topic、Key、消费组的设计;接着解释为什么异步,生产确认、消费提交与幂等如何配合;最后讲实际验证过的故障、监控指标和改进结果。

以订单事件设计为例:

1
2
3
4
5
6
业务:订单创建后,通知与统计需要独立处理。
事件:带有稳定 eventId,使用 orderId 作为 Key。
分工:通知与统计各自使用消费组。
一致性:数据库与 Outbox 同事务,投递器确认并恢复发送。
消费:业务与去重同事务,成功后推进分区进度。
观测:分区 Lag、处理耗时、发送错误、提交失败和 Rebalance。

这是设计示例。学习阶段可以表述为“我做过本地实验”或“如果设计这个业务,我会……”,并具体讲清已验证的部分。吞吐量、节点规模、故障案例与效果数字,都应来自自己的实际记录。


六、三个故障场景追问

场景一:三个副本,只剩 Leader,为什么写不进去了?

已知:副本数 3,acks=all,min.insync.replicas=2,当前 ISR 只有 Leader。

回答顺序:

  1. 当前同步副本数量不足,不能满足成功写入的最低要求。
  2. 配置在保护冗余要求,生产者需要处理失败并保留事件。
  3. 先排查副本失效或落后的原因,不应直接把最低 ISR 改为 1 来消除报错。
  4. 若业务选择降低门槛,需要明确接受的故障风险,并有恢复与对账措施。

这个问题考的是配置背后的取舍,不只是参数含义。

场景二:数据库已经扣库存,提交 Offset 前崩溃了,会发生什么?

**回答:**恢复后可能从旧提交位置再次消费。如果扣库存与去重记录在同一个事务中已经成功提交,重复事件会被指定唯一约束识别,不再重复扣减;然后再推进消费进度。

继续追问:“如果先写去重标记,再开另一个事务扣库存呢?”

**回答:**中间崩溃会留下“已处理”标记,但库存实际没扣,重试又被标记挡住。因此去重记录与业务结果必须共享成功或回滚的边界。

场景三:Offset 100 成功、101 失败、102 成功,应该提交多少?

在普通连续日志的这个例子中,最多推进到 101,表示 100 已完成,恢复时仍需要尝试 101。

不能提交 103,因为那会覆盖失败的 101。102 虽然已完成,也可能在恢复后重复,因此还需要幂等。

继续追问:“那是不是提交最大的成功 Offset 加一就行?”

**回答:**不行,要维护该分区已完成处理的连续边界,不能跨过失败或未完成的记录。这里的连续指处理顺序上的完整前缀,不要求实际可见 Offset 数字永远没有间隙。


七、面试前快速复习

7.1 六个容易答错的结论

容易答错的说法 更准确的表达
acks=all 保证任何情况都不丢 要结合副本、ISR、应用恢复和故障假设
生产者幂等等于业务幂等 它处理特定协议重试,不替代业务去重
earliest 每次都从 0 开始 没有有效位置时,从当前保留的最早位置开始
消息进入线程池就可以提交 应按业务完成的分区边界推进
相同 Key 就能保证业务有序 还要保证路由稳定、源头顺序和执行顺序
Kafka 事务会一起提交 MySQL 原子范围需要明确,外部副作用另行设计

7.2 用这几个参数串起知识点

参数 面试时应该连到哪个问题
acks 生产者等待什么确认
min.insync.replicas 同步副本不足时是否继续确认写入
enable.idempotence 协议级重试如何避免重复写入
delivery.timeout.ms 一次发送的整体交付时间与结果不确定性
group.id 哪些消费者共享分工与进度
enable.auto.commit 提交时机能否对齐业务完成
auto.offset.reset 没有有效位置时如何恢复
max.poll.records 单次返回给应用的记录数量
max.poll.interval.ms 处理过慢与轮询间隔
group.protocol classic 与新 consumer 协议的区别
isolation.level 是否只读取已提交事务记录

7.3 60 秒整体回答

Kafka 可以理解为一个分布式事件日志平台。Topic 通过分区实现并行,分区通过副本提供冗余,消费组独立管理分工与进度。可靠性需要沿生产、存储和消费三段设计:生产端观察发送结果,存储端结合 ACK 与 ISR,消费端在业务成功后提交进度,并用业务幂等处理重放。顺序性主要是分区级保证,还依赖 Key、发送顺序和业务执行方式。Kafka 到 Kafka 的处理可以用事务结合输出和输入进度,数据库双写则需要 Outbox 等方案。排查积压时,要看分区热点、处理耗时、提交失败和 Rebalance,而不是只增加消费者。

复习时可以围绕一个问题继续练习:如果现在这个位置崩溃,哪些数据已经保存,哪些进度还没提交,恢复后会发生什么? 能把这个故障窗口讲清楚,参数与机制就容易连起来。