Kafka 消息不丢失实战:生产者 ack + 副本 ISR + 消费者手动提交的完整配置

引言

一个安静得可怕的对账日:

风控月度对账发现:37 笔已支付成功的订单,下游库存服务完全没有收到"支付成功"消息。Kafka 集群日志没有任何 ERROR,Producer 端没有回调异常,Consumer 端没有报错——消息就是凭空消失了。

复盘结论更扎心:这不是一次故障,是三个"配置漏洞"的接力——生产者 acks=1 时恰好赶上分区 Leader 切换、Broker 没设 min.insync.replicas(ISR 缩到 1 也照收不误)、消费者开了自动提交(先提交 offset 后处理,业务处理失败消息就"确认掉了")。三层各漏一个,消息就没了。Kafka 从不承诺"你配错了我也不丢",它只承诺"你配对了我不丢"。

这篇文章按消息的生命周期拆三层保障:生产者端(acks + retries + 幂等)→ Broker 端(副本 + ISR + Unclean 选举)→ 消费者端(手动提交)。每一层讲三件事:消息怎么丢的、每条配置的语义是什么、为什么必须这么设。先放结论总表(完整可抄清单在第七章):

层配置设定值防住什么代价
生产者acksallLeader 挂了消息没同步到副本延迟 +,吞吐 -30%
生产者enable.idempotencetrue重试导致的重复与乱序几乎无
生产者retriesInteger.MAX_VALUE瞬时故障直接放弃发送无(配合幂等)
Brokerreplication.factor≥3单机故障丢数据成本 ×3
Brokermin.insync.replicas2ISR 缩水还在写(裸奔防线)ISR<2 时拒写
Brokerunclean.leader.electionfalse数据落后的副本当选 Leader故障时可能不可用
消费者enable.auto.commitfalse处理前就提交 offset需要幂等消费兜底
消费者手动 commitSync业务成功后处理失败可重投至少一次 → 可能重复

一句话破题:Kafka 的消息不丢失是一条"责任链"——生产者保证"送达 Broker 的副本",Broker 保证"副本没同步齐不交付 Leader 位",消费者保证"处理完才确认"。三层任何一层松手,另两层救不回来。


一、先搞清楚:消息会在哪里丢

1.1 消息生命周期的丢失风险地图

producer ──①发送──▶ broker Leader ──②副本同步──▶ ISR Followers
                        │                              │
                        │ ③Leader 挂/切换               │
                        ▼                              ▼
   【丢失点 A】acks=1:数据只到 Leader,Leader 宕机未同步
   【丢失点 B】min.insync.replicas 未设:ISR 缩到 1(只剩 Leader)仍接收写入
   【丢失点 C】unclean.leader.election=true:落后的副本上位,已确认消息被"覆盖"
                        │
                        ▼
consumer ──④拉取──▶ 业务处理 ──⑤提交 offset
                        │
   【丢失点 D】自动提交:offset 先于业务处理提交
               → 处理失败/进程崩溃,消息被标记"已消费"
   【丢失点 E】异步处理无确认:拉了没处理完就停

五个丢失点,A/B/C 在生产侧与 Broker 侧,D/E 在消费侧——"消息丢了"从来不是单点问题,是把三层防线拆开看的必然结果。本次事故 37 笔消息,就是 A(当时分区正在切换)+ B(ISR 缩到 1 没拒写)+ D(自动提交抢跑)的组合。

1.2 先建立"确认"的心智模型

消息系统里"不丢"的本质是确认链的完整传递:

Producer 收到 ack  ⇔  消息已被"足够多的 Broker"持久接收
Broker 交付写入    ⇔  ISR 副本数 ≥ min.insync.replicas
Consumer 确认     ⇔  业务处理完成才提交 offset

任何一环的"确认"早于事实,窗口期内的故障 = 丢消息

记住这个模型,第二章到第四章的所有配置都只是在收紧每一环的"确认条件"。


二、生产者端:acks + 幂等,把"送达"定义清楚

2.1 acks 三档语义:你到底确认了什么

acks确认语义丢消息场景延迟适用
0发出去就当成功(不等 Broker 确认)网络抖动/Broker 宕机全丢最低日志采集等可容忍场景
1Leader 写入(PageCache)即确认Leader 确认后宕机、副本未同步 → 消息消失低一般业务(有丢的风险)
allISR 全部副本确认才算需配合 min.insync.replicas≥2 才真正可靠较高支付/订单等核心链路

注意一个容易忽略的细节:acks=1 的确认发生在 Leader 的 PageCache,连 Leader 自己的磁盘都没落——这不是 Bug,是设计(副本冗余代替单机 fsync,机制同《Kafka 为什么这么快》8.2)。acks=1 的可靠性完全押注在"Leader 活到副本追上"这个假设上,分区切换时假设就破了——事故里那 37 笔正是这样没的。

2.2 配置与"为什么"

# ── 生产者核心三件套 ──────────────────────────────
acks=all                        # ① 确认升级为"ISR 全部收到",防丢失点 A
enable.idempotence=true         # ② 幂等生产者(3.0+ 默认值,老版本显式配)
retries=2147483647              # ③ 不轻易放弃;瞬时故障交给重试
# ── 配套参数 ─────────────────────────────────────
max.in.flight.requests.per.connection=5   # 幂等上限即 5,防乱序需 ≤5
delivery.timeout.ms=120000      # 重试总预算 2 分钟,超时才真失败
request.timeout.ms=30000
compression.type=zstd           # 可靠性无损的流量优化(见《Kafka 为什么这么快》第五板斧)

为什么必须幂等(enable.idempotence):retries 重试会带来两个副作用——重复(Broker 其实收到了,ack 在网络中丢失,客户端重发)和乱序(batch1 失败重试时 batch2 先到)。幂等生产者用 <PID, 分区, 序列号> 三元组让 Broker 去重:每个分区记住最后的序列号,重发的旧序列号直接拒绝,并把"未确认的下一个 batch 阻塞住"保证顺序。开启后 retries 放到多大都不会造成重复/乱序——这就是"retries 必须配合幂等"的原因:裸重试是不安全的。

为什么 max.in.flight 必须 ≤5:幂等协议依赖 Broker 端按序列号判定,窗口超过 5 时 Broker 无法用有限缓存完成判定,Kafka 直接抛 OutOfOrderSequenceException 拒绝。默认值恰好是 5,改之前先想清楚。

为什么 delivery.timeout.ms 要显式设:retries=MAX 不等于无限重试,delivery.timeout.ms 是总闸(发送+重试全算)。2 分钟的预算对支付链路意味着:故障 2 分钟内自动恢复的消息一个不丢,超时的失败会被业务感知并进入补偿——比"静默重试一小时"更适合上游有对账的体系。

2.3 发送侧的代码纪律:回调必须处理

producer.send(record, (metadata, exception) -> {
    if (exception != null) {
        // ★ 不处理回调 = 配了 acks=all 也白配:
        // 发送失败被静默吞掉,业务以为发出去了
        log.error("send failed, topic={}, key={}", record.topic(), record.key(), exception);
        compensateTable.insert(record);   // 进补偿表,对账兜底
    }
});

acks=all 管"网络到 Broker"的可靠,回调处理管"Broker 到业务"的可靠——两层衔接点就是这个回调。事故复盘里还有一笔隐蔽损失:11 笔消息其实发送失败了(超时),但代码没传回调,连补偿的机会都没有。


三、Broker 端:副本 + ISR,把"交付"的门槛立起来

3.1 ISR 是什么:同步副本集合

分区副本:Leader + 2 个 Follower

ISR(In-Sync Replicas)= 当前与 Leader 保持同步的副本集合
  ── 判定标准:replica.lag.time.max.ms(默认 30s)内追上 Leader 的最高水位 HW
  ── Follower 网络抖动/磁盘慢/GC → 移出 ISR
  ── 追上后 → 重新加入 ISR

ISR 正常:{1, 2, 3}   → acks=all 的消息要 3 个副本都收到
ISR 缩水:{1}        → 只剩 Leader,acks=all 退化成 acks=1 !

最后一行是整个 Broker 侧防线的核心认知:acks=all 的"all"指 ISR 成员,不是全部副本。ISR 缩到 1 时,acks=all 悄悄变成 acks=1,可靠性瞬间归零——min.insync.replicas 就是防这个退化的闸门。

3.2 Topic 级配置与"为什么"

# ── Topic 级(建 Topic 时指定,核心 Topic 必须)─────────
replication.factor=3              # ① 副本数:扛住单机故障的最低配置
min.insync.replicas=2             # ② ISR 最低水位:低于 2 拒绝写入
unclean.leader.election.enable=false  # ③ 禁止非 ISR 副本当选 Leader

# ── Broker 级(全局兜底)──────────────────────────────
default.replication.factor=3
min.insync.replicas=2             # Topic 未显式配置时生效
unclean.leader.election.enable=false
auto.create.topics.enable=false   # 防止裸配置的 Topic 被随手创建

为什么 replication.factor ≥3:与《PG 高可用实战》的仲裁逻辑同构——3 是"允许 1 台故障仍有多数"的最小奇数。2 副本时任何一台故障,min.insync.replicas=2 直接拒写(不可用),1 副本则毫无冗余。

为什么 min.insync.replicas=2:把"acks=all 的实际确认门槛"从 1(ISR 可缩到 1)抬到 2。此时 ISR={1} 时 producer 会收到 NOT_ENOUGH_REPLICAS 异常拒写——用短时不可用换"绝不写入没有冗余的数据"。这行配置是acks=all 真正生效的前提,只配 acks 不配它等于没配。

为什么 unclean.leader.election=false:Unclean 选举允许"数据落后的副本"当 Leader。假设 Leader(offset=1000)宕机,副本 B 只同步到 offset=980,Unclean 选举让 B 上位——offset 980~1000 的 20 条已确认消息被判定"从未存在",消息无声无息地被"时间倒流"。设为 false 后宁可用 Leader 不可用(分区只读)也不让数据回退:"不可用"是故障,"丢数据"是事故,二者不可兼得时选前者。

3.3 一个必须写进值班手册的连锁反应

Broker 2 宕机
  → 分区 p0 的 ISR 从 {1,2,3} 缩到 {1,3}
  → min.insync.replicas=2 仍满足,继续写入(正常)
Broker 3 也抖动被移出 ISR
  → ISR = {1},低于 2
  → 该分区拒绝写入(NOT_ENOUGH_REPLICAS),业务收到发送失败
  → 正确处置:恢复 Broker(重启副本重新入 ISR),而不是改小 min.insync.replicas!
    ★ 改小配置 = 把"暂时的不可用"换成"永久的风险敞口",事故复盘里见过的最差处置

四、消费者端:手动提交,把"确认"的时机还给业务

4.1 自动提交的两个丢失窗口

# 事故配置(已废除)
enable.auto.commit=true
auto.commit.interval.ms=5000

自动提交由消费者客户端按时间间隔提交"当前拉取到的最大 offset"——提交与业务处理之间没有任何关联。两个窗口:

【窗口一:先提交后处理】
poll 到 msg#100~110 → 定时器到点,自动提交 offset=110
                     → 处理 #107 时抛异常 → 重启后从 #111 开始
                     → #107~110 永远不会被再次处理 = 丢了

【窗口二:处理中崩溃】
poll 到 msg#100~110 → 处理到 #105 进程被 kill
                     → 最近一次自动提交在 3 秒前(offset=100 之前?之后?不确定)
                     → 若已提交到 108 → #106~108 丢失

**根因是同一个:offset 的"确认"早于业务的"完成"。**手动提交把顺序倒过来。

4.2 手动提交模板(可直接抄)

props.put(ENABLE_AUTO_COMMIT_CONFIG, false);          // ① 关自动提交

@KafkaListener(topics = "order-pay", groupId = "inventory")
public void onMessage(ConsumerRecord<String, String> record, Acknowledgment ack) {
    try {
        inventoryService.deduct(record);              // ② 业务处理
        ack.acknowledge();                            // ③ 成功才提交
    } catch (BizException e) {
        // 可恢复业务异常:重试或进重试队列,不提交(会被重投)
        retryQueue.send(record);
        ack.acknowledge();                            // 已转存,可提交防重复拉取
    } catch (Exception e) {
        // 不可预期异常:不提交,下次重新拉取
        log.error("consume failed, offset={}", record.offset(), e);
        throw e;   // 让错误可见(Spring Kafka 会按容器策略处理)
    }
}
spring:
  kafka:
    listener:
      ack-mode: MANUAL      # Spring Kafka 配合 Acknowledgment
      type: single          # 单条确认,粒度最细(batch 模式见 FAQ 4)
    consumer:
      max-poll-records: 100 # 单次拉取量与处理能力匹配

为什么用 commitSync 而不是 commitAsync:同步提交阻塞等 Broker 确认,失败会重试,语义上是"尽力确保提交成功";异步提交快但可能静默失败(offset 实际没提交成功)。消费提交不在毫秒级敏感路径上,可靠性优先——核心链路用 commitSync,高吞吐旁路才考虑 async+定期 sync 混合。

为什么必须接受"重复":手动提交的时序是"处理 → 提交",两步之间崩溃就会重复消费(处理了但没提交,重启重拉)。这不是缺陷是必然:不丢和唯一不可兼得,只能二选一——选了"不丢"(至少一次),就要用幂等消费兜住重复。幂等方案三件套(唯一索引 + Redis SETNX + 状态机)在《消息重复消费排查》里有完整实现,此处不重复展开,只强调一句:本篇的"不丢"配置,必须和那篇的"幂等"配置成对出现,才是完整方案。

4.3 消费端的两个配套参数

参数设定为什么
max.poll.interval.ms> 单批最大处理时长处理超时会触发 rebalance,拉取的批次被重新分配 = 变相重复;设大 + 监控处理耗时
max.poll.records与处理能力匹配(100)拉太多处理不完,崩溃时未提交的重复范围就大

五、三层联动:一条消息的"不丢失"全链路

producer                          broker                            consumer
   │                                 │                                  │
   │ send(msg, acks=all)             │                                  │
   │  ├─ ①Leader 追加(PageCache)    │                                  │
   │  ├─ ②Follower 同步               │                                  │
   │  └─ ③ISR 全部确认 ──────────────▶│                                  │
   │     ★ min.insync.replicas=2     │                                  │
   │       ISR<2 → 拒写(无冗余不上)  │                                  │
   │ ◀── ack 回调 ──┤                 │                                  │
   │   失败→retries+幂等去重          │                                  │
   │   超时→补偿表                    │                                  │
   │                                 │                                  │
   │                                 │ ◀── poll 批次 ────────────────────│
   │                                 │      (offset 尚未提交)           │
   │                                 │      业务处理(幂等兜底重复)        │
   │                                 │      成功 ── commitSync ─────────▶│
   │                                 │      ★ 处理成功才确认              │
   │                                 │      失败 → 不提交 → 重投          │

崩溃恢复语义:
  生产侧:acks=all 未确认 → 重试(幂等去重)/ 补偿 → 不丢
  Broker:ISR≥2 的消息单机故障不丢;ISR<2 宁可拒写
  消费侧:未确认的消息必然重投 → 不丢(代价:可能重复 → 幂等)

三层配置不是并列的三个开关,而是一条首尾相接的责任链:生产者的 acks=all 依赖 Broker 的 ISR 门槛才有意义;消费者的"处理成功才确认"以"Broker 不会弄丢已确认消息"为前提;而整条链的"至少一次"语义,最终由消费端的幂等收口。

5.1 可靠性的价格:实测数据

同一环境(3 副本、NVMe、2KB 消息、32 分区)压测:

配置组合吞吐P99 延迟说明
acks=1 + 自动提交82 万 msg/s8 ms事故前的"裸奔配置"
acks=all+幂等 + 手动提交(推荐)58 万 msg/s14 ms-29% 吞吐,换零丢失
acks=all + min.isr=3(更保守)52 万 msg/s15 ms再 -10%,容忍 2 台故障

-29% 的吞吐里,大部分能靠批量参数(batch.size=64KB、linger.ms=5、zstd)追回来——《Kafka 为什么这么快》的四板斧与可靠性配置正交,先上可靠性基线,再用吞吐优化补性能,顺序别反。


六、常见问题

6.1 acks=all 配了,为什么消息还是丢了?

按顺序查三件事:① Topic 的 min.insync.replicas 没配或为 1——ISR 缩到 1 时 acks=all 退化成 acks=1(第三章核心),kafka-topics.sh --describe 看 Configs;② 发送回调没处理——发送失败被静默吞掉(2.3 的 11 笔就是这种);③ unclean.leader.election 开着——故障切换时落后副本上位"覆盖"了消息。三层各查一遍,丢点必然在其中之一。

6.2 enable.idempotence 和事务(transactional.id)什么关系?我该用哪个?

幂等只保证单分区内的去重与顺序;Kafka 事务(transactional.id)额外保证跨分区/跨会话的原子写入(要么全成功要么全不可见),典型场景是"消费-转换-生产"(Kafka Streams / exactly-once 管道)。普通业务生产者用幂等就够了:事务引入 transaction.timeout、Coordinator 协调、消费者要配 isolation.level=read_committed 一整串复杂度——为用不到的语义买单。判断标准:你的"一次业务动作"是否跨多个分区写入且要求原子可见,是才上事务。

6.3 min.insync.replicas=2 导致发送频繁失败(NOT_ENOUGH_REPLICAS),能不能改成 1?

不能,这是把事故的导火索拆成定时炸弹。频繁 NOT_ENOUGH_REPLICAS 说明 Broker 有健康问题(磁盘慢/网络抖/GC),正确动作是查 Broker 而不是调低防线。紧急止血的正确姿势:临时扩容分区副本(kafka-reassign-partitions.sh 把副本迁到健康 Broker),恢复 ISR≥2;若确实要降级业务可用性,用应用层降级开关(关键消息落库+延迟补发),Broker 配置保持不动。

6.4 消费吞吐大,单条 commitSync 太慢怎么办?

三个档位按需选:① batch 拉取 + 批处理完 commitSync 一次(Spring Kafka type: batch)——提交次数 ÷ 批大小,重复范围从单条变单批(幂等键按批内最细粒度兜);② 每 N 条或每 T 秒 sync 一次(ack-mode: COUNT | TIME)——折中,重复窗口 = N 条/ T 秒;③ commitAsync + 定期 commitSync 混合——吞吐最高,接受偶发提交失败(下次 poll 会覆盖)。原则:吞吐档位的提升,都在放大"重复消费"的范围,幂等消费是所有档位的前置条件。

6.5 37 笔消息已经丢了,怎么补?

两个恢复路径:① 源头重放——支付系统是权威源,按对账差异单号列表,从支付服务重新发一遍消息(走当前可靠配置 + 下游幂等);② binlog 补偿——如果消息是从 DB 变更派生的(canal 类链路),按 binlog 位点重放。补完后在补偿流水打标记,纳入对账验证闭环。教训沉淀:消息丢了不可怕,可怕的是没有"权威源可重放"——所以核心链路的架构评审里,"这条消息能不能重放、从哪重放"应该是必答项。

6.6 这套配置影响我现有 Topic 吗?迁移怎么做?

三层里两层是客户端配置(改了重启即生效),只有 Broker/Topic 层动存量 Topic 要谨慎:min.insync.replicas 从 1 调到 2 时,如果存量分区 ISR 恰好 <2 会立即拒写。安全节奏:先扩副本/修 Broker 确保 ISR 健康(describe 确认全部 ≥2),低峰再调配置,观察 NOT_ENOUGH_REPLICAS 报错为零后完成。unclean.leader.election 从 true 改 false 无需担心——它只影响"下一次故障切换",但要让值班知道:某些极端故障下分区可能长时间只读,处置预案从"自动选举"变成"人工介入"。


七、完整配置清单(可直接抄)

7.1 生产者

acks=all
enable.idempotence=true
retries=2147483647
delivery.timeout.ms=120000
request.timeout.ms=30000
max.in.flight.requests.per.connection=5
compression.type=zstd
batch.size=65536          # 吞吐补偿(四板斧)
linger.ms=5
# 代码纪律:send 必须带回调,失败进补偿表

7.2 Broker / Topic

# Broker 级
default.replication.factor=3
min.insync.replicas=2
unclean.leader.election.enable=false
auto.create.topics.enable=false

# 核心 Topic 建立时
replication.factor=3
min.insync.replicas=2

7.3 消费者

enable.auto.commit=false
max.poll.records=100
max.poll.interval.ms=300000
# Spring Kafka:listener.ack-mode=MANUAL,业务成功后 ack.acknowledge()
# 配套:消费幂等(唯一索引/SETNX/状态机)——《消息重复消费排查》

八、总结

决策树

消息丢了怎么防(按丢失点对号):
├── 发送侧丢?
│   ├── acks 是 0/1                      → 改 all(配 min.insync.replicas=2)
│   ├── 回调没处理                        → 补回调 + 补偿表
│   └── retries=0 / 无幂等                → retries=MAX + enable.idempotence
├── Broker 侧丢?
│   ├── min.insync.replicas 未设/为 1     → 设 2(先确认 ISR 健康)
│   ├── unclean.leader.election=true      → 改 false + 更新值班预案
│   └── 副本数 < 3                        → reassign 扩副本
├── 消费侧丢?
│   ├── enable.auto.commit=true           → 改 false + MANUAL ack
│   ├── 处理失败没进重试队列               → 失败分支落重试/DLQ
│   └── max.poll.interval 超时 rebalance  → 调大 + 监控处理耗时
└── 全配好了还担心?
    └── 消费幂等收口重复 + 对账体系 + 重放预案(6.5)

三层速查表

层核心机制关键配置一句话原理
生产者确认送达acks=all + 幂等 + retries"ISR 全收到才叫成功,重试不重复不乱序"
Broker交付门槛rf=3 + min.isr=2 + 禁 Unclean"没有冗余的数据不交付,落后副本不上位"
消费者确认消费关自动提交 + 处理后 commitSync"业务完成才确认,确认必然可能重复"

一句话

"Kafka 不丢消息"是一句有前置条件的承诺,条件就是三层各自收紧"确认"的时机:生产者把 acks 从"Leader 收到了"升级为"ISR 全部收到了",并用幂等协议让重试变得安全(PID+分区+序列号,Broker 端去重保序),用永不放弃的 retries 加有限超时预算让瞬时故障自动愈合;Broker 端用 min.insync.replicas=2 堵住 acks=all 最大的暗门——ISR 缩到 1 时 all 静默退化为 1,宁可短暂拒写(NOT_ENOUGH_REPLICAS)也不接收没有冗余的数据,用 unclean.leader.election=false 拒绝"数据落后的副本上位导致时间倒流",这三行 Topic 配置的哲学与《PG 高可用实战》一脉相承:不可用是故障,丢数据是事故,两者冲突时永远选前者;消费者端把 offset 的提交时机从"定时器说了算"还给"业务说了算",处理成功才 commitSync,代价是崩溃时必然重复——所以本篇的每一行"不丢"配置,都必须与《消息重复消费排查》的幂等方案成对出现才能闭环:不丢失与不重复在异步系统里不可兼得,至少一次 + 幂等是唯一可持续的组合;最后记住两层容易被忽略的衔接点——send 回调是"网络可靠性"到"业务可靠性"的接口,对账与重放预案是"最后一公里"的兜底,37 笔消息消失的事故没有神秘力量,只有三个各松一扣的配置漏洞:把每一扣拧紧,Kafka 的承诺才真正兑现。

关键数据

  • acks=1 → acks=all+min.isr=2:吞吐 82 万 → 58 万 msg/s(-29%),P99 8→14 ms,换零丢失
  • 吞吐缺口可用四板斧追回大半(batch.size/linger/zstd 与可靠性正交)
  • 幂等协议开销:<2% 吞吐,却让 retries=MAX 变得安全
  • min.insync.replicas=1 → 2:ISR 缩水时从"静默降级"变为"明确拒写"
  • 事故账单:37 笔丢失 = acks=1(Leader 切换)+ ISR 缩水未拒写 + 自动提交抢跑,三层各漏一扣

给团队的建议

项目状态建议
新集群/新 Topic上线即用第七章完整清单,别让裸配置进生产
存量 Topic 改造按 6.6 节节奏:先保 ISR 健康、低峰调参、监控 NOT_ENOUGH_REPLICAS
发送代码无回调全量排查 send 调用点,回调 + 补偿表作为 CR 硬性要求
消费端全自动提交列入整改清单,与消费幂等一起做(成对上线)
无对账/重放预案核心链路评审必答:"消息丢了从哪重放、谁负责对账"
值班手册收录 3.3 的 ISR 缩水连锁反应与"禁止调小 min.isr"红线

互动话题:你们踩过消息"凭空消失"的坑吗——是发送侧静默失败、ISR 缩水,还是自动提交抢跑?你见过最离谱的"找回消息"操作是什么?评论区聊聊,点赞最高的丢消息案例我会做一次完整复盘,并列出"如果当时配对了会发生什么"的对照表。



标题:Kafka 消息不丢失实战:生产者 ack + 副本 ISR + 消费者手动提交的完整配置
作者:jiangyi
地址:http://jiangyi.space/articles/2026/10/23/1792334209629.html
公众号:服务端技术精选
    评论
    0 评论
avatar

取消