【kafka系列】At Least Once语义

目录

[1. At-Least-Once语义的定义](#1. At-Least-Once语义的定义)

[2. Kafka实现At-Least-Once的机制](#2. Kafka实现At-Least-Once的机制)

[2.1 生产者端](#2.1 生产者端)

[2.2 消费者端](#2.2 消费者端)

[3. At-Least-Once示例](#3. At-Least-Once示例)

场景描述

[3.1 生产者代码(可能重复发送)](#3.1 生产者代码(可能重复发送))

[3.2 消费者代码(可能重复处理)](#3.2 消费者代码(可能重复处理))

[4. 典型重复场景分析](#4. 典型重复场景分析)

场景1:生产者重试导致消息重复

场景2:消费者重复处理消息

[5. 业务端如何应对重复](#5. 业务端如何应对重复)

[5.1 幂等性处理](#5.1 幂等性处理)

[5.2 消费者端去重](#5.2 消费者端去重)

[6. At-Least-Once vs Exactly-Once](#6. At-Least-Once vs Exactly-Once)

[7. 总结](#7. 总结)


1. At-Least-Once语义的定义

At-Least-Once(至少一次) 语义指:

  • 消息从生产者到Broker:确保消息至少成功写入一次(可能因重试导致重复)。
  • 消息从Broker到消费者:确保消息至少被消费一次(可能因消费者重复拉取导致处理多次)。

核心特点消息不丢失,但可能重复


2. Kafka实现At-Least-Once的机制

2.1 生产者端
  • 配置 acks=all:确保消息被所有ISR副本写入后才返回成功。
  • 开启重试( retries > 0**)**:若Broker未及时响应,生产者自动重试发送消息。
  • 风险:若Broker已写入但未返回ACK(如网络超时),生产者重试会导致消息重复。
2.2 消费者端
  • 手动提交Offset :消费者处理完消息后手动调用commitSync()提交Offset。
  • 风险:若消费者处理消息后崩溃,未提交Offset,下次启动时会重新拉取消息,导致重复处理。

3. At-Least-Once示例

场景描述

一个用户积分系统:

  • 生产者 :发送用户积分增加消息到Topic user_points
  • 消费者 :消费消息,为用户增加积分,并提交Offset。
    要求:积分必须至少增加一次(允许重复增加,但需业务端处理重复)。

3.1 生产者代码(可能重复发送)
java 复制代码
// 生产者配置(At-Least-Once)
Properties props = new Properties();
props.put("bootstrap.servers", "kafka1:9092");
props.put("acks", "all");      // 确保消息写入所有ISR副本
props.put("retries", 3);       // 重试3次

KafkaProducer<String, String> producer = new KafkaProducer<>(props);

// 发送积分消息(可能重复)
producer.send(new ProducerRecord<>("user_points", "user-1001", "+10"));

潜在问题

若Broker写入成功但网络超时,生产者重试会导致消息重复发送到Topic。


3.2 消费者代码(可能重复处理)
java 复制代码
// 消费者配置(At-Least-Once)
Properties props = new Properties();
props.put("bootstrap.servers", "kafka1:9092");
props.put("group.id", "points-group");
props.put("enable.auto.commit", "false"); // 关闭自动提交

KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Collections.singletonList("user_points"));

while (true) {
    ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
    for (ConsumerRecord<String, String> record : records) {
        // 处理消息:为用户增加积分
        addUserPoints(record.key(), Integer.parseInt(record.value()));
        // 手动提交Offset(可能失败)
        consumer.commitSync();
    }
}

潜在问题

addUserPoints()执行成功但commitSync()失败(如消费者崩溃),下次启动时会重新拉取并处理同一消息,导致积分重复增加。


4. 典型重复场景分析

场景1:生产者重试导致消息重复
  • 原因:Broker已写入消息但ACK丢失,生产者重试发送。
  • 结果 :Topic中存在两条相同消息(如user-1001, +10)。
场景2:消费者重复处理消息
  • 原因:消费者处理消息后未提交Offset(如崩溃)。
  • 结果:重启后重新拉取并处理同一消息,积分被重复增加。

5. 业务端如何应对重复

5.1 幂等性处理
  • 数据库唯一约束:为每条消息生成唯一ID,插入时去重。
sql 复制代码
INSERT INTO user_points (user_id, points, msg_id) 
VALUES ('user-1001', 10, 'uuid-123') 
ON CONFLICT (msg_id) DO NOTHING;
  • 业务逻辑幂等:设计接口支持多次调用结果一致。
java 复制代码
// 幂等积分增加方法
public void addPoints(String userId, int points, String msgId) {
    if (!isMessageProcessed(msgId)) {
        updateUserPoints(userId, points);
        markMessageAsProcessed(msgId);
    }
}
5.2 消费者端去重
  • 本地记录已处理消息:使用缓存或数据库记录已处理的Offset或消息ID。
java 复制代码
// 消费者处理逻辑(伪代码)
for (ConsumerRecord record : records) {
    if (!processedMessages.contains(record.offset())) {
        addUserPoints(record.key(), record.value());
        processedMessages.add(record.offset());
        consumer.commitSync();
    }
}

6. At-Least-Once vs Exactly-Once

|--------|-------------------|-----------------------|
| 特性 | At-Least-Once | Exactly-Once |
| 消息丢失风险 | 不丢失 | 不丢失 |
| 消息重复风险 | 可能重复 | 不重复 |
| 性能开销 | 低(无需事务) | 高(事务与协调开销) |
| 适用场景 | 允许重复的业务(如日志采集) | 金融交易、精准统计 |
| 实现复杂度 | 简单 | 复杂(需生产者、Broker、消费者协同) |


7. 总结

  • At-Least-Once是Kafka的默认语义 :通过acks=all和手动提交Offset实现,简单高效。
  • 业务端必须处理重复:通过幂等性设计或去重机制避免数据不一致。
  • 适用场景:日志采集、指标上报等允许少量重复但对丢失敏感的场景。

通过合理配置和业务设计,At-Least-Once可平衡可靠性与性能,是大多数场景的推荐选择。

相关推荐
hi星尘9 分钟前
深入理解Apache Kafka
分布式·kafka·apache
IT成长日记17 分钟前
【Kafka基础】监控与维护:分区健康检查,确保数据高可用
分布式·kafka·健康检查·监控与维护
黑眼圈的小熊猫22 分钟前
Git开发
大数据·git·elasticsearch
涛思数据(TDengine)22 分钟前
虚拟表、TDgpt、JDBC 异步写入…TDengine 3.3.6.0 版本 8 大升级亮点
大数据·数据库·tdengine
betazhou35 分钟前
sqlserver2017 分离附加数据库
数据库·oracle·sqlserver
我是小木鱼39 分钟前
浅析Centos7安装Oracle12数据库
linux·运维·服务器·数据库
Arbori_2621542 分钟前
oracle 表空间(Tablespace)
数据库·oracle
茉莉玫瑰花茶1 小时前
线程同步与互斥(下)
linux·数据库
BUG 劝退师1 小时前
MySQL数据库编程总结
数据库·mysql
goTsHgo1 小时前
Flink的 RecordWriter 数据通道 详解
大数据·flink