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

相关推荐
小杜谈数25 分钟前
信息系统项目管理工程师备考计算类真题讲解十四
大数据
odng26 分钟前
Redis如何实现分布式锁
数据库·redis·分布式
长流小哥1 小时前
MySQL C API高效编程:C语言实现数据库操作的深入解析
数据库·c++·mysql
JhonKI1 小时前
【MySQL】存储引擎 - MEMORY详解
数据库·mysql
pqq的迷弟1 小时前
redis多路复用IO模型 以及 6.0引入的多线程模型
数据库·redis·php
蒋大钊!1 小时前
RocketMQ与Kafka的区别
kafka·rocketmq
Agatha方艺璇2 小时前
Hadoop MapReduce 图文代码讲解
大数据·hadoop·mapreduce
wuli玉shell2 小时前
Spark存储级别
大数据·分布式·spark
larance2 小时前
Django rest_framework 信号机制生成并使用token
数据库·django·sqlite
悻运2 小时前
Spark缓存
大数据·缓存·spark