欢迎光临
我们一直在努力

Kafka消息漏消费深度解析:原因、场景与解决方案

Kafka消息漏消费深度解析:原因、场景与解决方案

    • 一、漏消费 vs 重复消费
      • 1.1 两种异常的本质对比
      • 1.2 核心区别
    • 二、漏消费的六大典型场景
      • 2.1 场景一:先提交Offset,后处理消息(最典型)
      • 2.2 场景二:自动提交 + 处理异常
      • 2.3 场景三:批量处理中的部分失败
      • 2.4 场景四:多线程处理不当
      • 2.5 场景五:事务性操作不一致
      • 2.6 场景六:再均衡时的处理不当
    • 三、漏消费场景分类总结
      • 3.1 场景分类表
      • 3.2 漏消费 vs 重复消费对比
    • 四、解决方案与最佳实践
      • 4.1 方案一:正确处理提交顺序
      • 4.2 方案二:手动提交 + 精细控制
      • 4.3 方案三:原子性提交(消息表模式)
      • 4.4 方案四:禁用自动提交
      • 4.5 方案五:再均衡监听器的正确处理
    • 五、各种场景的解决方案速查表
    • 六、监控与预防
      • 6.1 监控指标
      • 6.2 预防性配置
    • 七、面试高频问题
      • Q1:漏消费和重复消费有什么区别?
      • Q2:哪些情形会造成消息漏消费?
      • Q3:如何避免漏消费?
      • Q4:自动提交有什么风险?
      • Q5:如何处理批量处理中的部分失败?
    • 八、总结
      • 8.1 核心原则
      • 8.2 安全消费公式
      • 8.3 一句话总结

🌺The Begin🌺点点关注,收藏不迷路🌺

关键词:Kafka漏消费、Offset提交、消息丢失、消费顺序、数据一致性、消费者配置

在消息队列系统中,漏消费(消息丢失) 是最严重的问题之一,它意味着数据永久性丢失,可能导致业务数据不一致、统计错误等严重后果。

今天,我们将深入剖析Kafka中消息漏消费的各种情形,与重复消费形成对比,并给出完整的预防和解决方案。


一、漏消费 vs 重复消费

1.1 两种异常的本质对比

#mermaid-svg-884klZRagH4iKbe6{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-884klZRagH4iKbe6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-884klZRagH4iKbe6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-884klZRagH4iKbe6 .error-icon{fill:#552222;}#mermaid-svg-884klZRagH4iKbe6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-884klZRagH4iKbe6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-884klZRagH4iKbe6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-884klZRagH4iKbe6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-884klZRagH4iKbe6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-884klZRagH4iKbe6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-884klZRagH4iKbe6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-884klZRagH4iKbe6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-884klZRagH4iKbe6 .marker.cross{stroke:#333333;}#mermaid-svg-884klZRagH4iKbe6 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-884klZRagH4iKbe6 p{margin:0;}#mermaid-svg-884klZRagH4iKbe6 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-884klZRagH4iKbe6 .cluster-label text{fill:#333;}#mermaid-svg-884klZRagH4iKbe6 .cluster-label span{color:#333;}#mermaid-svg-884klZRagH4iKbe6 .cluster-label span p{background-color:transparent;}#mermaid-svg-884klZRagH4iKbe6 .label text,#mermaid-svg-884klZRagH4iKbe6 span{fill:#333;color:#333;}#mermaid-svg-884klZRagH4iKbe6 .node rect,#mermaid-svg-884klZRagH4iKbe6 .node circle,#mermaid-svg-884klZRagH4iKbe6 .node ellipse,#mermaid-svg-884klZRagH4iKbe6 .node polygon,#mermaid-svg-884klZRagH4iKbe6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-884klZRagH4iKbe6 .rough-node .label text,#mermaid-svg-884klZRagH4iKbe6 .node .label text,#mermaid-svg-884klZRagH4iKbe6 .image-shape .label,#mermaid-svg-884klZRagH4iKbe6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-884klZRagH4iKbe6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-884klZRagH4iKbe6 .rough-node .label,#mermaid-svg-884klZRagH4iKbe6 .node .label,#mermaid-svg-884klZRagH4iKbe6 .image-shape .label,#mermaid-svg-884klZRagH4iKbe6 .icon-shape .label{text-align:center;}#mermaid-svg-884klZRagH4iKbe6 .node.clickable{cursor:pointer;}#mermaid-svg-884klZRagH4iKbe6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-884klZRagH4iKbe6 .arrowheadPath{fill:#333333;}#mermaid-svg-884klZRagH4iKbe6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-884klZRagH4iKbe6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-884klZRagH4iKbe6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-884klZRagH4iKbe6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-884klZRagH4iKbe6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-884klZRagH4iKbe6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-884klZRagH4iKbe6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-884klZRagH4iKbe6 .cluster text{fill:#333;}#mermaid-svg-884klZRagH4iKbe6 .cluster span{color:#333;}#mermaid-svg-884klZRagH4iKbe6 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-884klZRagH4iKbe6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-884klZRagH4iKbe6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-884klZRagH4iKbe6 .icon-shape,#mermaid-svg-884klZRagH4iKbe6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-884klZRagH4iKbe6 .icon-shape p,#mermaid-svg-884klZRagH4iKbe6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-884klZRagH4iKbe6 .icon-shape rect,#mermaid-svg-884klZRagH4iKbe6 .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-884klZRagH4iKbe6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-884klZRagH4iKbe6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-884klZRagH4iKbe6 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

漏消费

先提交Offset

处理失败

拉取消息

提交成功

处理消息

消息丢失

重复消费

处理成功但提交失败

重启/再均衡

拉取消息

处理消息

提交失败

重新消费

正常流程

拉取消息

处理消息

提交Offset

1.2 核心区别

问题发生时机结果严重程度
重复消费 处理成功,提交失败 消息被多次处理 中等(需幂等处理)
漏消费 提交成功,处理失败 消息永久丢失 严重(数据丢失)

二、漏消费的六大典型场景

2.1 场景一:先提交Offset,后处理消息(最典型)

// ❌ 错误写法:先提交,后处理
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));

// 先提交Offset
consumer.commitSync(); // 这里提交了这批消息的Offset

// 后处理消息
for (ConsumerRecord<String, String> record : records) {
processRecord(record); // 如果这里抛出异常,消息已无法重新消费
// 因为Offset已经提交了!
}
}

后果:如果processRecord抛出异常,这批消息已经提交了Offset,Kafka认为它们已被消费。但实际业务处理失败,消息丢失!

2.2 场景二:自动提交 + 处理异常

// 自动提交配置
props.put("enable.auto.commit", "true");
props.put("auto.commit.interval.ms", "5000"); // 5秒提交一次

while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));

for (ConsumerRecord<String, String> record : records) {
processRecord(record); // 假设处理到第3条时抛出异常

// 如果此时已经到达5秒自动提交阈值
// Kafka自动提交了Offset
// 但还有消息未处理完,这些消息就丢失了!
}
}

2.3 场景三:批量处理中的部分失败

// 批量处理,但部分失败
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));

// 批量处理
batchProcessor.process(records); // 假设批量处理中,部分成功,部分失败

// 提交Offset(所有消息的Offset)
consumer.commitSync();

// 问题:成功处理的消息正常,但失败的消息也提交了Offset,永久丢失!
}

2.4 场景四:多线程处理不当

// ❌ 错误的多线程模型
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));

// 在线程池中异步处理
executorService.submit(() -> {
for (ConsumerRecord<String, String> record : records) {
processRecord(record);
}
// 异步线程中提交Offset
consumer.commitSync(); // ❌ 多线程共享consumer,极其危险!
});

// 主线程继续poll
// 如果异步线程处理慢,主线程可能已经poll了新数据
// Offset提交混乱,可能导致消息丢失
}

2.5 场景五:事务性操作不一致

// 数据库操作成功,但业务逻辑后续失败
@Transactional
public void processWithTransaction(ConsumerRecord<String, String> record) {
// 1. 数据库操作(成功)
jdbcTemplate.update("INSERT INTO orders VALUES (?)", record.value());

// 2. 提交Offset(但Kafka事务和数据库事务是两套)
consumer.commitSync(); // Offset提交成功

// 3. 后续业务操作(比如调用外部API)
externalApi.call(); // 这里可能失败抛出异常

// 数据库已更新,Offset已提交,但外部调用失败
// 业务状态不一致,且消息无法重试
}

2.6 场景六:再均衡时的处理不当

// 再均衡监听器中处理不当
consumer.subscribe(Arrays.asList("topic"), new ConsumerRebalanceListener() {
@Override
public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
// 分区被回收时,提交当前已处理的Offset
// 但如果此时还有正在处理但未完成的消息
consumer.commitSync(currentOffsets); // 提交了部分处理的Offset

// 正在处理的消息被中断,且无法恢复
}

@Override
public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
// 从上次提交的Offset开始消费
// 那些被中断的消息就丢失了
}
});


三、漏消费场景分类总结

3.1 场景分类表

类别具体场景根本原因发生频率
提交时机 先提交后处理 顺序错误
自动提交 自动提交+异常 提交时机不可控
批量处理 部分失败 原子性缺失
多线程 共享consumer 线程安全问题
事务不一致 两套事务系统 分布式事务难题
再均衡 处理中断 生命周期管理不当

3.2 漏消费 vs 重复消费对比

维度漏消费重复消费
发生条件 先提交,后处理失败 先处理成功,后提交失败
结果 数据永久丢失 数据多次处理
严重性 ⚠️ 严重 ⚠️ 中等
解决方案 确保处理后提交 幂等性设计
典型场景 自动提交、异常未捕获 消费者崩溃、Rebalance

四、解决方案与最佳实践

4.1 方案一:正确处理提交顺序

// ✅ 正确写法:处理后提交
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));

// 先处理消息
for (ConsumerRecord<String, String> record : records) {
try {
processRecord(record); // 处理消息
} catch (Exception e) {
// 记录失败的消息,进行补偿处理
handleFailure(record, e);
// 注意:这里需要决定是否继续处理还是停止
}
}

// 全部处理完成(或失败处理完成)后,再提交Offset
consumer.commitSync();
}

4.2 方案二:手动提交 + 精细控制

// 手动提交,精确控制每个分区的Offset
public class SafeConsumer {
private final Map<TopicPartition, OffsetAndMetadata> currentOffsets = new HashMap<>();

public void consume() {
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));

for (ConsumerRecord<String, String> record : records) {
// 处理消息
processRecord(record);

// 记录每个分区的处理进度
currentOffsets.put(
new TopicPartition(record.topic(), record.partition()),
new OffsetAndMetadata(record.offset() + 1)
);

// 每处理100条提交一次
if (currentOffsets.size() >= 100) {
consumer.commitSync(currentOffsets);
currentOffsets.clear();
}
}

// 提交剩余的Offset
if (!currentOffsets.isEmpty()) {
consumer.commitSync(currentOffsets);
currentOffsets.clear();
}
}
}
}

4.3 方案三:原子性提交(消息表模式)

// 使用数据库事务保证原子性
@Service
public class ExactlyOnceProcessor {

@Autowired
private JdbcTemplate jdbcTemplate;

@Autowired
private KafkaConsumer<String, String> consumer;

@Transactional
public void processWithTransaction(ConsumerRecord<String, String> record) {
// 1. 检查是否已处理(幂等性检查)
Integer count = jdbcTemplate.queryForObject(
"SELECT COUNT(*) FROM processed_messages WHERE message_id = ?",
Integer.class, getMessageId(record));

if (count > 0) {
return; // 已处理,跳过
}

// 2. 业务操作
jdbcTemplate.update("INSERT INTO orders VALUES (?)", record.value());

// 3. 记录已处理的消息ID(与业务操作在同一事务中)
jdbcTemplate.update(
"INSERT INTO processed_messages VALUES (?, ?)",
getMessageId(record), System.currentTimeMillis()
);

// 4. 记录要提交的Offset(也放在同一事务中)
jdbcTemplate.update(
"INSERT INTO consumer_offsets VALUES (?, ?, ?)",
record.topic(), record.partition(), record.offset() + 1
);

// 数据库事务提交后,再加载Offset并提交
// 注意:这里需要另一个线程定期从数据库加载Offset并提交到Kafka
}
}

4.4 方案四:禁用自动提交

// 绝对禁用自动提交
Properties props = new Properties();
props.put("enable.auto.commit", "false"); // 必须手动提交

// 其他相关配置
props.put("auto.commit.interval.ms", "0"); // 确保自动提交关闭
props.put("session.timeout.ms", "30000");
props.put("max.poll.interval.ms", "300000");

4.5 方案五:再均衡监听器的正确处理

public class SafeRebalanceConsumer {
private final Map<TopicPartition, OffsetAndMetadata> currentOffsets = new ConcurrentHashMap<>();
private final Map<TopicPartition, List<ConsumerRecord>> processingRecords = new ConcurrentHashMap<>();

public void consume() {
consumer.subscribe(Arrays.asList("topic"), new ConsumerRebalanceListener() {
@Override
public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
// 等待正在处理的消息完成(有超时机制)
waitForProcessingComplete(partitions, 30, TimeUnit.SECONDS);

// 提交已处理的Offset
if (!currentOffsets.isEmpty()) {
consumer.commitSync(currentOffsets);
currentOffsets.clear();
}
}

@Override
public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
// 可以在这里做一些初始化
System.out.println("分配了新分区:" + partitions);
}
});

while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));

for (ConsumerRecord<String, String> record : records) {
TopicPartition tp = new TopicPartition(record.topic(), record.partition());

// 记录正在处理的消息
processingRecords.computeIfAbsent(tp, k -> new ArrayList<>()).add(record);

// 处理消息
processRecord(record);

// 处理完成,移除
processingRecords.get(tp).remove(record);

// 更新Offset
currentOffsets.put(tp, new OffsetAndMetadata(record.offset() + 1));
}

// 定期提交
if (currentOffsets.size() > 0) {
consumer.commitSync(currentOffsets);
currentOffsets.clear();
}
}
}

private void waitForProcessingComplete(Collection<TopicPartition> partitions, long timeout, TimeUnit unit) {
long deadline = System.currentTimeMillis() + unit.toMillis(timeout);
for (TopicPartition tp : partitions) {
while (!processingRecords.getOrDefault(tp, Collections.emptyList()).isEmpty()) {
if (System.currentTimeMillis() > deadline) {
System.out.println("等待处理完成超时:" + tp);
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
}


五、各种场景的解决方案速查表

场景解决方案实现方式
先提交后处理 调整顺序 处理后提交
自动提交 禁用自动提交 enable.auto.commit=false
批量处理部分失败 精确提交 记录每个消息的Offset
多线程问题 每个线程独立Consumer 不共享Consumer
事务不一致 消息表模式 同事务记录状态
再均衡中断 等待处理完成 RebalanceListener中等待

六、监控与预防

6.1 监控指标

// 监控可能漏消费的指标
public class LossMonitor {
private final Metrics metrics = new Metrics();

public void monitorConsumer(KafkaConsumer<String, String> consumer) {
// 监控提交频率
metrics.meter("commit.rate").mark();

// 监控处理异常
metrics.counter("process.error").inc();

// 监控提交与处理的差值
long processed = getProcessedCount();
long committed = getCommittedOffset();

if (processed < committed) {
// 处理的比提交的少,可能有漏消费风险
metrics.counter("potential.data.loss").inc();
alert("可能存在数据丢失风险!");
}
}
}

6.2 预防性配置

// 预防漏消费的关键配置
Properties props = new Properties();

// 1. 禁用自动提交
props.put("enable.auto.commit", "false");

// 2. 设置合理的超时
props.put("session.timeout.ms", "30000");
props.put("max.poll.interval.ms", "300000");

// 3. 设置重试
props.put("retries", "3");

// 4. 设置隔离级别(事务相关)
props.put("isolation.level", "read_committed");

// 5. 设置合理的拉取大小
props.put("max.poll.records", "500");


七、面试高频问题

Q1:漏消费和重复消费有什么区别?

答:

  • 漏消费:先提交Offset后处理失败,消息永久丢失
  • 重复消费:先处理成功但提交失败,消息被重复处理
  • 漏消费比重复消费更严重,因为数据丢失无法恢复

Q2:哪些情形会造成消息漏消费?

答:

  • 先提交Offset,后处理消息
  • 自动提交配置下,处理异常
  • 批量处理中的部分失败
  • 多线程共享Consumer
  • 事务性操作不一致
  • 再均衡时处理中断
  • Q3:如何避免漏消费?

    答:

  • 始终坚持处理后提交的顺序
  • 禁用自动提交,使用手动提交
  • 精确控制每个分区的Offset
  • 使用消息表模式保证原子性
  • 正确处理再均衡事件
  • Q4:自动提交有什么风险?

    答:自动提交的风险在于提交时机不可控:

    • 可能在处理完成前提交
    • 可能在处理异常时自动提交
    • 无法精细控制提交粒度
    • 生产环境强烈建议禁用自动提交

    Q5:如何处理批量处理中的部分失败?

    答:

  • 记录每个消息的处理状态
  • 只提交成功处理的消息的Offset
  • 将失败的消息写入死信队列或重试队列
  • 使用事务保证原子性

  • 八、总结

    8.1 核心原则

    先处理,后提交;宁可重复,不可丢失

    #mermaid-svg-Db97Jbse3Gl8LegC{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Db97Jbse3Gl8LegC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Db97Jbse3Gl8LegC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Db97Jbse3Gl8LegC .error-icon{fill:#552222;}#mermaid-svg-Db97Jbse3Gl8LegC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Db97Jbse3Gl8LegC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Db97Jbse3Gl8LegC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Db97Jbse3Gl8LegC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Db97Jbse3Gl8LegC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Db97Jbse3Gl8LegC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Db97Jbse3Gl8LegC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Db97Jbse3Gl8LegC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Db97Jbse3Gl8LegC .marker.cross{stroke:#333333;}#mermaid-svg-Db97Jbse3Gl8LegC svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Db97Jbse3Gl8LegC p{margin:0;}#mermaid-svg-Db97Jbse3Gl8LegC .edge{stroke-width:3;}#mermaid-svg-Db97Jbse3Gl8LegC .section–1 rect,#mermaid-svg-Db97Jbse3Gl8LegC .section–1 path,#mermaid-svg-Db97Jbse3Gl8LegC .section–1 circle,#mermaid-svg-Db97Jbse3Gl8LegC .section–1 polygon,#mermaid-svg-Db97Jbse3Gl8LegC .section–1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-Db97Jbse3Gl8LegC .section–1 text{fill:#ffffff;}#mermaid-svg-Db97Jbse3Gl8LegC .node-icon–1{font-size:40px;color:#ffffff;}#mermaid-svg-Db97Jbse3Gl8LegC .section-edge–1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-Db97Jbse3Gl8LegC .edge-depth–1{stroke-width:17;}#mermaid-svg-Db97Jbse3Gl8LegC .section–1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-Db97Jbse3Gl8LegC .disabled,#mermaid-svg-Db97Jbse3Gl8LegC .disabled circle,#mermaid-svg-Db97Jbse3Gl8LegC .disabled text{fill:lightgray;}#mermaid-svg-Db97Jbse3Gl8LegC .disabled text{fill:#efefef;}#mermaid-svg-Db97Jbse3Gl8LegC .section-0 rect,#mermaid-svg-Db97Jbse3Gl8LegC .section-0 path,#mermaid-svg-Db97Jbse3Gl8LegC .section-0 circle,#mermaid-svg-Db97Jbse3Gl8LegC .section-0 polygon,#mermaid-svg-Db97Jbse3Gl8LegC .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-Db97Jbse3Gl8LegC .section-0 text{fill:black;}#mermaid-svg-Db97Jbse3Gl8LegC .node-icon-0{font-size:40px;color:black;}#mermaid-svg-Db97Jbse3Gl8LegC .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-Db97Jbse3Gl8LegC .edge-depth-0{stroke-width:14;}#mermaid-svg-Db97Jbse3Gl8LegC .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-Db97Jbse3Gl8LegC .disabled,#mermaid-svg-Db97Jbse3Gl8LegC .disabled circle,#mermaid-svg-Db97Jbse3Gl8LegC .disabled text{fill:lightgray;}#mermaid-svg-Db97Jbse3Gl8LegC .disabled text{fill:#efefef;}#mermaid-svg-Db97Jbse3Gl8LegC .section-1 rect,#mermaid-svg-Db97Jbse3Gl8LegC .section-1 path,#mermaid-svg-Db97Jbse3Gl8LegC .section-1 circle,#mermaid-svg-Db97Jbse3Gl8LegC .section-1 polygon,#mermaid-svg-Db97Jbse3Gl8LegC .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-Db97Jbse3Gl8LegC .section-1 text{fill:black;}#mermaid-svg-Db97Jbse3Gl8LegC .node-icon-1{font-size:40px;color:black;}#mermaid-svg-Db97Jbse3Gl8LegC .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-Db97Jbse3Gl8LegC .edge-depth-1{stroke-width:11;}#mermaid-svg-Db97Jbse3Gl8LegC .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-Db97Jbse3Gl8LegC .disabled,#mermaid-svg-Db97Jbse3Gl8LegC .disabled circle,#mermaid-svg-Db97Jbse3Gl8LegC .disabled text{fill:lightgray;}#mermaid-svg-Db97Jbse3Gl8LegC .disabled text{fill:#efefef;}#mermaid-svg-Db97Jbse3Gl8LegC .section-2 rect,#mermaid-svg-Db97Jbse3Gl8LegC .section-2 path,#mermaid-svg-Db97Jbse3Gl8LegC .section-2 circle,#mermaid-svg-Db97Jbse3Gl8LegC .section-2 polygon,#mermaid-svg-Db97Jbse3Gl8LegC .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-Db97Jbse3Gl8LegC .section-2 text{fill:#ffffff;}#mermaid-svg-Db97Jbse3Gl8LegC .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-Db97Jbse3Gl8LegC .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-Db97Jbse3Gl8LegC .edge-depth-2{stroke-width:8;}#mermaid-svg-Db97Jbse3Gl8LegC .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-Db97Jbse3Gl8LegC .disabled,#mermaid-svg-Db97Jbse3Gl8LegC .disabled circle,#mermaid-svg-Db97Jbse3Gl8LegC .disabled text{fill:lightgray;}#mermaid-svg-Db97Jbse3Gl8LegC .disabled text{fill:#efefef;}#mermaid-svg-Db97Jbse3Gl8LegC .section-3 rect,#mermaid-svg-Db97Jbse3Gl8LegC .section-3 path,#mermaid-svg-Db97Jbse3Gl8LegC .section-3 circle,#mermaid-svg-Db97Jbse3Gl8LegC .section-3 polygon,#mermaid-svg-Db97Jbse3Gl8LegC .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-Db97Jbse3Gl8LegC .section-3 text{fill:black;}#mermaid-svg-Db97Jbse3Gl8LegC .node-icon-3{font-size:40px;color:black;}#mermaid-svg-Db97Jbse3Gl8LegC .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-Db97Jbse3Gl8LegC .edge-depth-3{stroke-width:5;}#mermaid-svg-Db97Jbse3Gl8LegC .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-Db97Jbse3Gl8LegC .disabled,#mermaid-svg-Db97Jbse3Gl8LegC .disabled circle,#mermaid-svg-Db97Jbse3Gl8LegC .disabled text{fill:lightgray;}#mermaid-svg-Db97Jbse3Gl8LegC .disabled text{fill:#efefef;}#mermaid-svg-Db97Jbse3Gl8LegC .section-4 rect,#mermaid-svg-Db97Jbse3Gl8LegC .section-4 path,#mermaid-svg-Db97Jbse3Gl8LegC .section-4 circle,#mermaid-svg-Db97Jbse3Gl8LegC .section-4 polygon,#mermaid-svg-Db97Jbse3Gl8LegC .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-Db97Jbse3Gl8LegC .section-4 text{fill:black;}#mermaid-svg-Db97Jbse3Gl8LegC .node-icon-4{font-size:40px;color:black;}#mermaid-svg-Db97Jbse3Gl8LegC .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-Db97Jbse3Gl8LegC .edge-depth-4{stroke-width:2;}#mermaid-svg-Db97Jbse3Gl8LegC .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-Db97Jbse3Gl8LegC .disabled,#mermaid-svg-Db97Jbse3Gl8LegC .disabled circle,#mermaid-svg-Db97Jbse3Gl8LegC .disabled text{fill:lightgray;}#mermaid-svg-Db97Jbse3Gl8LegC .disabled text{fill:#efefef;}#mermaid-svg-Db97Jbse3Gl8LegC .section-5 rect,#mermaid-svg-Db97Jbse3Gl8LegC .section-5 path,#mermaid-svg-Db97Jbse3Gl8LegC .section-5 circle,#mermaid-svg-Db97Jbse3Gl8LegC .section-5 polygon,#mermaid-svg-Db97Jbse3Gl8LegC .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-Db97Jbse3Gl8LegC .section-5 text{fill:black;}#mermaid-svg-Db97Jbse3Gl8LegC .node-icon-5{font-size:40px;color:black;}#mermaid-svg-Db97Jbse3Gl8LegC .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-Db97Jbse3Gl8LegC .edge-depth-5{stroke-width:-1;}#mermaid-svg-Db97Jbse3Gl8LegC .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-Db97Jbse3Gl8LegC .disabled,#mermaid-svg-Db97Jbse3Gl8LegC .disabled circle,#mermaid-svg-Db97Jbse3Gl8LegC .disabled text{fill:lightgray;}#mermaid-svg-Db97Jbse3Gl8LegC .disabled text{fill:#efefef;}#mermaid-svg-Db97Jbse3Gl8LegC .section-6 rect,#mermaid-svg-Db97Jbse3Gl8LegC .section-6 path,#mermaid-svg-Db97Jbse3Gl8LegC .section-6 circle,#mermaid-svg-Db97Jbse3Gl8LegC .section-6 polygon,#mermaid-svg-Db97Jbse3Gl8LegC .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-Db97Jbse3Gl8LegC .section-6 text{fill:black;}#mermaid-svg-Db97Jbse3Gl8LegC .node-icon-6{font-size:40px;color:black;}#mermaid-svg-Db97Jbse3Gl8LegC .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-Db97Jbse3Gl8LegC .edge-depth-6{stroke-width:-4;}#mermaid-svg-Db97Jbse3Gl8LegC .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-Db97Jbse3Gl8LegC .disabled,#mermaid-svg-Db97Jbse3Gl8LegC .disabled circle,#mermaid-svg-Db97Jbse3Gl8LegC .disabled text{fill:lightgray;}#mermaid-svg-Db97Jbse3Gl8LegC .disabled text{fill:#efefef;}#mermaid-svg-Db97Jbse3Gl8LegC .section-7 rect,#mermaid-svg-Db97Jbse3Gl8LegC .section-7 path,#mermaid-svg-Db97Jbse3Gl8LegC .section-7 circle,#mermaid-svg-Db97Jbse3Gl8LegC .section-7 polygon,#mermaid-svg-Db97Jbse3Gl8LegC .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-Db97Jbse3Gl8LegC .section-7 text{fill:black;}#mermaid-svg-Db97Jbse3Gl8LegC .node-icon-7{font-size:40px;color:black;}#mermaid-svg-Db97Jbse3Gl8LegC .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-Db97Jbse3Gl8LegC .edge-depth-7{stroke-width:-7;}#mermaid-svg-Db97Jbse3Gl8LegC .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-Db97Jbse3Gl8LegC .disabled,#mermaid-svg-Db97Jbse3Gl8LegC .disabled circle,#mermaid-svg-Db97Jbse3Gl8LegC .disabled text{fill:lightgray;}#mermaid-svg-Db97Jbse3Gl8LegC .disabled text{fill:#efefef;}#mermaid-svg-Db97Jbse3Gl8LegC .section-8 rect,#mermaid-svg-Db97Jbse3Gl8LegC .section-8 path,#mermaid-svg-Db97Jbse3Gl8LegC .section-8 circle,#mermaid-svg-Db97Jbse3Gl8LegC .section-8 polygon,#mermaid-svg-Db97Jbse3Gl8LegC .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-Db97Jbse3Gl8LegC .section-8 text{fill:black;}#mermaid-svg-Db97Jbse3Gl8LegC .node-icon-8{font-size:40px;color:black;}#mermaid-svg-Db97Jbse3Gl8LegC .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-Db97Jbse3Gl8LegC .edge-depth-8{stroke-width:-10;}#mermaid-svg-Db97Jbse3Gl8LegC .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-Db97Jbse3Gl8LegC .disabled,#mermaid-svg-Db97Jbse3Gl8LegC .disabled circle,#mermaid-svg-Db97Jbse3Gl8LegC .disabled text{fill:lightgray;}#mermaid-svg-Db97Jbse3Gl8LegC .disabled text{fill:#efefef;}#mermaid-svg-Db97Jbse3Gl8LegC .section-9 rect,#mermaid-svg-Db97Jbse3Gl8LegC .section-9 path,#mermaid-svg-Db97Jbse3Gl8LegC .section-9 circle,#mermaid-svg-Db97Jbse3Gl8LegC .section-9 polygon,#mermaid-svg-Db97Jbse3Gl8LegC .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-Db97Jbse3Gl8LegC .section-9 text{fill:black;}#mermaid-svg-Db97Jbse3Gl8LegC .node-icon-9{font-size:40px;color:black;}#mermaid-svg-Db97Jbse3Gl8LegC .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-Db97Jbse3Gl8LegC .edge-depth-9{stroke-width:-13;}#mermaid-svg-Db97Jbse3Gl8LegC .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-Db97Jbse3Gl8LegC .disabled,#mermaid-svg-Db97Jbse3Gl8LegC .disabled circle,#mermaid-svg-Db97Jbse3Gl8LegC .disabled text{fill:lightgray;}#mermaid-svg-Db97Jbse3Gl8LegC .disabled text{fill:#efefef;}#mermaid-svg-Db97Jbse3Gl8LegC .section-10 rect,#mermaid-svg-Db97Jbse3Gl8LegC .section-10 path,#mermaid-svg-Db97Jbse3Gl8LegC .section-10 circle,#mermaid-svg-Db97Jbse3Gl8LegC .section-10 polygon,#mermaid-svg-Db97Jbse3Gl8LegC .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-Db97Jbse3Gl8LegC .section-10 text{fill:black;}#mermaid-svg-Db97Jbse3Gl8LegC .node-icon-10{font-size:40px;color:black;}#mermaid-svg-Db97Jbse3Gl8LegC .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-Db97Jbse3Gl8LegC .edge-depth-10{stroke-width:-16;}#mermaid-svg-Db97Jbse3Gl8LegC .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-Db97Jbse3Gl8LegC .disabled,#mermaid-svg-Db97Jbse3Gl8LegC .disabled circle,#mermaid-svg-Db97Jbse3Gl8LegC .disabled text{fill:lightgray;}#mermaid-svg-Db97Jbse3Gl8LegC .disabled text{fill:#efefef;}#mermaid-svg-Db97Jbse3Gl8LegC .section-root rect,#mermaid-svg-Db97Jbse3Gl8LegC .section-root path,#mermaid-svg-Db97Jbse3Gl8LegC .section-root circle,#mermaid-svg-Db97Jbse3Gl8LegC .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-Db97Jbse3Gl8LegC .section-root text{fill:#ffffff;}#mermaid-svg-Db97Jbse3Gl8LegC .section-root span{color:#ffffff;}#mermaid-svg-Db97Jbse3Gl8LegC .section-2 span{color:#ffffff;}#mermaid-svg-Db97Jbse3Gl8LegC .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-Db97Jbse3Gl8LegC .edge{fill:none;}#mermaid-svg-Db97Jbse3Gl8LegC .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-Db97Jbse3Gl8LegC :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    root(防漏消费核心原则)

    顺序

    先处理消息

    后提交Offset

    粒度

    精细控制

    每消息/每批提交

    原子性

    业务操作+状态记录

    同一事务

    监控

    提交进度

    处理进度

    差值预警

    8.2 安全消费公式

    安全消费 = 处理后提交 + 手动控制 + 幂等处理 + 异常恢复

    8.3 一句话总结

    漏消费的根源是"先提交后处理",解决方案是"处理后提交"加上"原子性保证"。

    掌握了这些原理和解决方案,你就能构建一个即使面对各种异常也能保证数据不丢失的可靠系统!


    思考题:在Kafka的Exactly-Once语义中,如何同时保证不重复和不丢失?Kafka的事务机制是如何解决这个问题的?欢迎在评论区讨论!

    在这里插入图片描述

    🌺The End🌺点点关注,收藏不迷路🌺

    赞(0)
    未经允许不得转载:171主机测评 » Kafka消息漏消费深度解析:原因、场景与解决方案
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址