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:哪些情形会造成消息漏消费?
答:
Q3:如何避免漏消费?
答:
Q4:自动提交有什么风险?
答:自动提交的风险在于提交时机不可控:
- 可能在处理完成前提交
- 可能在处理异常时自动提交
- 无法精细控制提交粒度
- 生产环境强烈建议禁用自动提交
Q5:如何处理批量处理中的部分失败?
答:
八、总结
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🌺点点关注,收藏不迷路🌺 |



