【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可平衡可靠性与性能,是大多数场景的推荐选择。

相关推荐
roman_日积跬步-终至千里34 分钟前
【分布式理论13】分布式存储:数据存储难题与解决之道
分布式
焱焱枫35 分钟前
自适应SQL计划管理(Adaptive SQL Plan Management)在Oracle 12c中的应用
数据库·sql·oracle
2301_7930698238 分钟前
Spring Boot +SQL项目优化策略,GraphQL和SQL 区别,Spring JDBC 等原理辨析(万字长文+代码)
java·数据库·spring boot·sql·jdbc·orm
hhw1991121 小时前
spring boot知识点5
java·数据库·spring boot
ITPUB-微风1 小时前
功能开关聚合对象实践:提升金融领域的高可用性
网络·数据库·金融
去看日出1 小时前
Linux(centos)系统安装部署MySQL8.0数据库(GLIBC版本)
linux·数据库·centos
(; ̄ェ ̄)。1 小时前
在Nodejs中使用kafka(三)offset偏移量控制策略,数据保存策略
分布式·后端·kafka·node.js
Hanyaoo2 小时前
为什么mvcc中?m_ids 列表并不等同于 min_trx_id 和 max_trx_id 之间的所有事务 ID
数据库
偏右右2 小时前
PL/SQL 异常处理
数据库·sql·oracle
binbinxyz3 小时前
【Kafka系列】Kafka 消息传递保障机制
分布式·kafka