首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >CKafka acks=all 仍可能丢:ISR 下限和 unclean 选举

CKafka acks=all 仍可能丢:ISR 下限和 unclean 选举

原创
作者头像
一只特立独行的兔先生
发布于 2026-11-13 12:19:55
发布于 2026-11-13 12:19:55
120
举报

支付回调、订单状态这类不能丢的消息,生产端把 acks 设成 all 就当成已经落稳。CKafka 新建 Topic 时,min.insync.replicas 默认是 1,unclean.leader.election.enable 默认是 true;Serverful 副本数默认是 2。Leader 异常切换时,已经返回成功的消息仍可能不在新 Leader 上。

acks=all 等的是当前 ISR

acks 管的是等不等确认,不管副本一共有几份。Apache Kafka 2.8 的生产者说明:acks=0 不等确认,retries 基本不起作用;acks=1 只等 Leader 写入本地日志,Leader 在副本跟上之前宕掉,消息会丢;acks=all(等同 -1)等当前 ISR 全部确认。保证是:只要还有至少一个当时的 ISR 副本活着,这条记录就不会丢。

「当前 ISR」不是全部副本。Follower 已经掉出 ISR 时,acks=all 不会等它。ISR 只剩 Leader,这次确认和 acks=1 没有区别。下一任 Leader 若来自当时的 ISR 之外,这句保证也不成立。

min.insync.replicas 只在 acks=all 或 -1 时生效。Kafka 2.8 的 Topic 配置写明:达不到下限,生产者收到 NotEnoughReplicas 或 NotEnoughReplicasAfterAppend。前者发生在写入前;后者是已经追加到 Leader 之后才发现 ISR 不够,再重试且没有幂等时可能重复。CKafka 控制台说明里有一句「当 producer 设置 request.required.acks 为 1 时……」,和这段语义对不上。acks=1 时下限不拒绝写入。

默认配置下的顺序是:Follower 落后被移出 ISR,只剩 Leader,acks=all 且下限为 1 仍能成功;随后 Leader 宕机,unclean 选举从落后副本里选新 Leader,已确认的消息不在上面。数据高可靠文档写明:不配合下限大于 1,ISR 只剩 Leader 时 acks=all 就退化为 acks=1。

控制台默认和副本数

配置

CKafka 控制台

Apache Kafka 2.8 Topic 默认

min.insync.replicas

1

1

unclean.leader.election.enable

true

false

Serverful 副本数

默认 2,可选 1、2、3

创建时指定

Serverless 副本数

固定 3,不能改

不适用

unclean 为 true 时,ISR 里选不出 Leader,就允许从未同步副本里选,控制台说明是优先可用性。Kafka 2.8 默认 false,并写明可能导致数据丢失。CKafka 数据高可靠文档建议关掉。没改过不等于跟开源一样安全。单副本可以选,文档明确不建议。

acks=1 在 Broker 意外中断时,落在 replica.lag.time.max.ms 里还没同步的消息可能丢。客户端参数文档写明该值目前是 10000 ms。计划内 rebalance(升级、切跨可用区、实例迁移)会先同步 offset,acks=1 不会因此丢。演练切换没丢,不能证明宕机也不丢。

要坏 1 台仍可写、且已确认的消息还在,需要副本数 3、下限 2、acks=all,并关掉 unclean。ISR 掉到 1 就拒绝写,而不是退回去只信 Leader。副本数 2 且下限 2,任一副本掉出 ISR 就不可写,比 3 副本更脆。副本数 3 且下限 3,坏 1 台就停写,只适合明确接受停写的场景。

Serverful 先加副本再改下限:Topic 列表、更多、更改副本数,低峰做,变更会迁数据。副本计入分区额度。文档例子是 1 个 Topic、6 分区、2 副本,额度 12;改成 3 副本是 18。Serverless 副本数不能改,但下限和 unclean 仍默认 1 和 true。3 副本的实际流量按生产流量乘 3 估算;生产带宽百分比不含副本流量,磁盘占用量包含副本。跨可用区把 acks 设成 all 会抬高 ack 等待耗时,不要因此改回 1。

生产者要写死

客户端默认值不跟 Broker 版本走。Kafka 2.8 生产者 acks 默认 1;3.0 起默认 all,没有冲突配置时默认开幂等。CKafka 有 2.8.1 和 3.2.3。3.x 客户端接到 2.8 实例,不写配置时 acks 已经是 all。客户端参数文档写的「不显式配置则默认 1」,对应 2.8 及更早客户端。订单链路在代码里写死。

示例里的 retries=0 不要抄。2.8 和 3.0 的 retries 默认都是 2147483647,真正的上限是 delivery.timeout.ms,默认 120000。retries=0 会让一次 ISR 不足直接失败;业务再无限重试,就和 AfterAppend 的窗口叠上。delivery.timeout.ms 必须大于等于 request.timeout.ms + linger.ms。request.timeout.ms 默认 30000,不能小于文档写明的 10000。

幂等只挡重试重复,不挡 unclean 丢消息。3.0 开幂等要求 acks=all、retries 大于 0、max.in.flight.requests.per.connection 不大于 5,默认就是 5。acks=1 且不显式开幂等,幂等会被关掉;显式打开再配 acks=1,抛 ConfigException。版本说明写明 Serverless 不支持事务和幂等,不要加 enable.idempotence。

代码语言:properties
复制
bootstrap.servers=10.0.0.12:9092
acks=all
enable.idempotence=true
delivery.timeout.ms=120000
request.timeout.ms=30000
linger.ms=100
max.in.flight.requests.per.connection=5

能打印出 partition 和 offset,按数据高可靠文档的说法,服务端已经保存这条消息。没有 offset 就不能对上游报成功。cause 里出现上面两个 ISR 异常,就是下限没满足。

代码语言:java
复制
import java.util.Properties;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.apache.kafka.common.serialization.StringSerializer;

Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "10.0.0.12:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.ACKS_CONFIG, "all");
props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
props.put(ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG, 120000);
props.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG, 30000);
try (KafkaProducer<String, String> producer = new KafkaProducer<>(props)) {
    try {
        RecordMetadata meta = producer.send(
            new ProducerRecord<>("orders", "order-1001", "order-1001")).get();
        System.out.printf("partition=%d offset=%d%n", meta.partition(), meta.offset());
    } catch (Exception ex) {
        Throwable cause = ex.getCause();
        System.err.println(cause != null ? cause : ex);
    }
}

改完要看 ISR

控制台:实例列表,进实例,Topic 列表,编辑,展开高级配置,核对下限和 unclean。自动创建的 Topic 默认仍是 1 和 true,改单个 Topic 不改这个默认,要改走配置 Topic 全局属性。

点 Topic 名称左侧的三角,看 leader、副本、ISR、未同步副本。ISR 个数小于副本个数,冗余已经没了。更多、查看 Topic 节点分布,确认同一分区的副本没有堆在一台 Broker 上。

代码语言:bash
复制
kafka-topics.sh --bootstrap-server 10.0.0.12:9092 --describe --topic orders

看 ReplicationFactor、Configs,以及每行 Replicas 和 Isr。开了 ACL 就补控制台给出的认证参数,不要把密码写进仓库。命令因缺认证失败,不代表配置没生效。专业版的未同步副本数把内置 Topic 也算进去,文档写正常应在 5 以下,长期大于 5 再处理。ack 等待耗时长期为 0,先确认 acks 是不是还是 1 或 0;文档写明这两种都不会产生等待耗时。

配反的几种做法

只加副本或只改 acks=all,只要下限仍是 1 且 unclean 仍是 true,已确认消息仍可能在选举里丢掉。下限设成和副本数一样,一次副本落后就停写。监控文档在 ISR 抖动、水位正常时建议把 ack 调成 1,那是保吞吐。

数据高可靠文档列出的 request.required.acks、message.send.max.retries 和 retry.backoff.ms=10000 是旧生产者参数;kafka-clients 2.8 和 3.x 用 acks 和 delivery.timeout.ms。退避拉到 10 秒,会把短抖动拖过业务超时。不要靠 flush.messages 替代副本:Kafka Topic 文档建议不要用它做持久性。

上线前核对

  • 副本数是 3,不是 Serverful 默认的 2。Serverless 不要开幂等。
  • min.insync.replicas 是 2,生产者 acks 显式为 all。acks=1 时下限不生效。
  • unclean.leader.election.enable 是 false。控制台默认 true,和高可靠文档的建议相反。
  • 分区展开后 ISR 个数等于副本个数,同一分区的副本不堆在一台 Broker 上。
  • retries 不是 0。delivery.timeout.ms 不小于 request.timeout.ms + linger.ms,request.timeout.ms 不小于 10000。
  • 成功日志有 partition 和 offset。失败能区分 ISR 不足和超时。业务侧仍有幂等键。

文档:CKafka 数据高可靠,创建 Topic,配置客户端参数,版本支持说明,Kafka 2.8 Topic 配置,Kafka 3.0 生产者配置。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • acks=all 等的是当前 ISR
  • 控制台默认和副本数
  • 生产者要写死
  • 改完要看 ISR
  • 配反的几种做法
  • 上线前核对
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档