tulip notes
首页
  • 学习笔记

    • 《Vue》
  • 踩坑日记

    • JavaScript
  • MQ
  • Nginx
  • IdentityServer
  • Redis
  • Linux
  • Java
  • SpringBoot
  • SpringCloud
  • MySql
  • docker
  • 算法与设计模式
  • 踩坑与提升
  • Git
  • GitHub技巧
  • Mac
  • 网络
  • 项目构建合集
  • 一些技巧
  • 面试
  • 一些杂货
  • 友情链接
  • 项目发布
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Star-Lord

希望一天成为大师的学徒
首页
  • 学习笔记

    • 《Vue》
  • 踩坑日记

    • JavaScript
  • MQ
  • Nginx
  • IdentityServer
  • Redis
  • Linux
  • Java
  • SpringBoot
  • SpringCloud
  • MySql
  • docker
  • 算法与设计模式
  • 踩坑与提升
  • Git
  • GitHub技巧
  • Mac
  • 网络
  • 项目构建合集
  • 一些技巧
  • 面试
  • 一些杂货
  • 友情链接
  • 项目发布
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 并发与锁相关

  • 对象与其他

  • 网络接口调用

  • 场景设计题

    • 数据不丢失与准确类
      • 积分系统-积分消费问题
        • 核心关注
        • 一些分析
        • 参考回答
        • 第一部分:如何保证消息100%不丢失(可靠性)
        • 第二部分:如何保证积分不重复发放(幂等性)
        • 追问
        • 1.“如果幂等表的数据量非常大怎么办?”
        • 2.“为什么不用Kafka自带的幂等Producer和事务功能?”
        • 4.“如果数据库插入成功了,但后续更新积分失败了怎么办?”
        • kafka扩展
        • kafka高效的原因
        • ack机制
        • 发送消息失败
        • broker端保证消息顺序和幂等
        • broker幂等
  • 架构与设计

  • 《踩坑与提升》记录
  • 场景设计题
EffectTang
2025-09-01
目录

数据不丢失与准确类

# 数据不丢失与准确类

# 积分系统-积分消费问题

在积分系统中,下单后需要发放积分。如果 Kafka 发送失败,怎么保证消息最终不会丢失,同时又避免重复发放?

这明显是一个中台或者电商场景下的实际痛点问题。用户可能正在准备中高级Java岗位的面试,需要展示对分布式系统数据一致性的深入理解。

这种问题往往没有一个完美的唯一的答案,往往有多种不同方案。有些可能性能更强,有些可能对数据的一致性保证更好,一个优秀的开发者或者架构师的回答,应该是体现架构思维、熟悉主流技术栈、并能平衡业务需求与技术复杂度的回答。

# 核心关注

该问题,需要同时处理消息可靠性与幂等性,还涉及异步流程的数据一致性。用户可能希望听到既有理论支撑(如CAP定理、幂等性设计),又能落地到具体技术(Kafka特性、Redis/Lock实现、DB事务整合)。

# 一些分析

首先明确这是分布式系统典型问题,必须同时处理可靠投递和消费幂等;然后分阶段讨论——生产端保证不丢(确认机制+重试),消费端保证不重(幂等设计+去重表);最后要给出技术选型建议和权衡(比如Redis幂等的性能好但持久性弱,DB方案更可靠但复杂)。

# 参考回答

# 第一部分:如何保证消息100%不丢失(可靠性)

消息传递有三个环节:生产端 -> Kafka Broker -> 消费端。要保证不丢,就需要在这三个环节都做保障。

1. 生产端 (Producer) 保证不丢

  • ACK确认机制:将Producer的 acks 设置为 all(或 -1)。这意味着Leader副本必须等待所有ISR(In-Sync Replicas)副本都成功接收到消息后,才会向Producer发送成功确认。这是最强程度的持久化保证。
  • 重试机制:设置一个合理的 retries 参数(如10次)和 retry.backoff.ms。当遇到网络抖动或Leader选举等瞬时故障时,Producer会自动重试发送,避免因一次失败就丢弃消息。
  • 同步发送+异常处理:在发送关键业务消息时,采用同步发送(future.get())或在异步发送的回调(Callback)中仔细处理异常。一旦发送失败,可以将消息持久化到本地数据库(或一个“发送中”状态表),然后启动一个补偿任务定时重试,直到发送成功才将本地状态更新为“已发送”。

2. Broker端保证不丢

  • 副本机制 (Replication):创建Topic时,设置 replication.factor >= 3。这样即使一个Broker节点宕机,数据也不会丢失。
  • ISR集合:Kafka的Leader会维护一个同步中的副本集合(ISR)。只有ISR中的副本才有资格被选举为新的Leader,这保证了数据的可靠性。
  • 持久化:Kafka本身的设计就是依赖磁盘顺序读写来持久化消息的,可靠性很高。

3. 消费端 (Consumer) 保证不丢

  • 手动提交偏移量 (Manual Offset Commit):禁用自动提交(enable.auto.commit = false),在业务逻辑成功执行完毕后再手动提交偏移量。
  • 正确的提交顺序:一定是先处理业务,再提交偏移量。如果顺序反了,业务处理失败但偏移量已提交,这条消息就永远丢失了。

小结一:通过 acks=all + 重试 + 副本 + 手动提交偏移量 这套组合拳,可以理论上保证消息在Kafka链条上不会被丢失。

# 第二部分:如何保证积分不重复发放(幂等性)

“解决了不丢的问题,由于网络重传、Consumer故障重启后的重试等原因,消息重复是必然会发生的事情。所以,我们必须让消费逻辑具备幂等性。”

幂等性 (Idempotence) 的意思是:同一个积分发放请求被多次执行,其效果与只执行一次的效果完全相同。

实现幂等性的常见方案:

  1. 数据库唯一键 (最常用、最有效)

    • 在发放积分的SQL语句执行前,先执行插入操作。我们可以创建一个幂等表(deduplication_table),表结构主要包含:
      • biz_id (String): 业务唯一ID,作为唯一索引或主键。
      • user_id (Long): 用户ID。
      • points (Int): 积分值。
      • created_at (DateTime): 创建时间。
    • 这个 biz_id 可以是订单号 + 业务类型(如:order_12345_points),必须能全局唯一标识这一笔积分发放业务。
    • 在消费消息时,先执行 INSERT INTO deduplication_table (biz_id, user_id, points) VALUES (?, ?, ?)。
    • 如果插入成功,说明是第一次处理,继续执行后续的积分增加操作。
    • 如果插入失败(触发了唯一键冲突异常),说明这条消息之前已经处理过了,直接丢弃消息或记录日志后确认消费成功即可。这样就避免了重复发放。
  2. Redis原子操作

  • 利用Redis的 SET key value NX EX timeout 命令。Key可以是同样的业务唯一ID biz_id。
  • NX 表示只有Key不存在时才能设置成功,这是一个原子操作。
  • 如果设置成功,说明是第一次处理,执行业务逻辑。
  • 如果设置失败,说明是重复消息,直接跳过。
  • 优点:性能极高。缺点:需要维护Redis的可靠性,可能存在数据丢失风险(尽管可以持久化),严格场景下不如数据库可靠。
  1. 版本号或状态机 (更复杂的业务)
  • 在用户积分账户表中增加一个 version 字段。
  • 更新积分时,带上版本号条件:UPDATE account SET points = points + 100, version = version + 1 WHERE user_id = ? AND version = ?。
  • 如果更新条数为0,说明版本号不对,可能是重复请求,不做处理。

小结二:首选方案是基于数据库唯一键的幂等设计,因为它简单、可靠,无需引入新技术栈,并且利用了数据库本身的能力。

所以,结合以上两点,我的最终方案是:

  1. 生产端:配置 acks=all 和充足的重试次数,同时做好发送失败后的落盘重试兜底方案,确保消息一定能到达Kafka。
  2. 消费端:采用手动提交偏移量,确保业务成功后再提交。并且,在积分发放的核心逻辑中,引入基于数据库唯一键的幂等设计,从根本上去除重复消息的影响。

此外,对于一个完备的系统,我们还可以加上定期对账的兜底措施,比如每天凌晨检查订单系统和积分系统的数据是否最终一致,及时发现并修复极端异常情况下可能产生的数据差异。”

“这个方案在保证了高可靠性的同时,也通过幂等性兼容了系统的高性能需求,是一个在实际项目中经过验证的成熟方案。”

# 追问

# 1.“如果幂等表的数据量非常大怎么办?”

  • 回答:可以定期归档历史数据,比如只保留最近3个月的幂等记录。因为对账系统可以覆盖历史数据的正确性,近期的不重复由幂等表保证,远期的由对账系统保证。

# 2.“为什么不用Kafka自带的幂等Producer和事务功能?”

  • 回答:Kafka的幂等Producer(enable.idempotence=true)只能保证单分区、单会话内消息不重复,即解决的是Producer端因重试导致的消息重复。而消费端的重复(如Consumer崩溃后重启)它无法解决。事务功能主要用于“精确一次(Exactly-Once)”语义,跨多个Topic-Partition的原子性写入,成本较高。对于我们这种单一积分Topic的场景,在消费端做业务幂等是更简单、更通用、性能也更好的选择。

# 4.“如果数据库插入成功了,但后续更新积分失败了怎么办?”

  • 回答:这是一个很好的问题。我们需要将插入幂等记录和更新积分账户放在同一个数据库事务里。这样,如果更新失败事务回滚,幂等记录也会被回滚掉,同一个消息下次过来依然会被当作新消息处理,直到最终成功。

# kafka扩展

# kafka高效的原因

处理数据顺序性,和零拷贝技术,还和kafka特殊的设计有关。

在发送过程中,生产者采用record accumulator和sender线程配合工作。Record accumulator作为消息累加器提前排队消息,按照partition进行有序排列;sender线程则从record accumulator中找到一批消息(默认5条,可配置)组成批量发送给broker,并在中心线程中等待broker的响应,以减少网络开销。

# ack机制

ACK是producer端的一个配置参数,用于控制消息发送后的确认方式。

0表示仅发送不关心broker是否成功接收,1表示要求broker将消息写入leader partition后确认,而-1或2则表示希望broker同步完成所有follower partition后再给producer确认。

# 发送消息失败

如果一条消息发送失败,其他消息还能否继续发送?如果一号消息发送失败,而二号到五号消息都成功发送到broker,那么broker如何处理这些消息顺序问题?

当一条消息发送失败时,其他消息(如二号、三号、四号、五号消息)仍然可以继续发送并一同发送给broker。这样设计是为了提升网络的吞吐量,即使有消息重试的情况,也能保证其他消息及时发送和处理。在这种情况下,broker需要保证消息的顺序和幂等性。它通过维护一个针对每个partition的唯一ID(PID)和序列号(sequence number)来记录消息的顺序。即使一号消息重试,broker也会根据序列号确保消息按照正确的顺序写入日志文件,同时避免重复写入已确认的消息。

# broker端保证消息顺序和幂等

在broker端,它是如何根据PID和sequence number保证消息的顺序和幂等性的?

在broker端,它会对PID和sequence number组成的二元组进行唯一性维护,并维护一个SN(序列号)。当接收到producer发送的消息时,broker会按照SN的顺序入队,只有当确认之前发送的消息已成功写入后,才会继续处理后续的消息。此外,对于重试的消息,broker基于相同的sequence number识别并避免重复写入已确认的消息,确保消息的安全性。

# broker幂等

消在息重试时,broker如何处理重复发送的消息?

当出现消息重试时,broker通过检查接收到的message的PID和sequence number来判断是否为重复消息。对于重复的消息,broker不会进行写入操作,而是按机制给予响应,让producer去做进一步处理,比如重新尝试发送或其他错误处理。

总结

broker处理消息机制:

  • 幂等性处理
    • PID与Sequence Number:保证消息唯一性与顺序
    • SN记录:记录PID与Sequence Number的顺序
  • 消息顺序与重复处理
    • 超时机制:处理长时间未到达的消息
    • 重复消息:识别并忽略重试消息
    • 乱序处理:等待缺失消息,保证顺序
上次更新: 2025/09/01, 15:13:14
HTTP调用:你考虑到超时、重试、并发了吗
景区门票管理和核销-1

← HTTP调用:你考虑到超时、重试、并发了吗 景区门票管理和核销-1→

最近更新
01
Spring中Bean的生命周期
09-03
02
线程池与任务调度
08-31
03
线程间通信与等待、通知机制
08-23
更多文章>
Theme by Vdoing | Copyright © 2023-2025 EffectTang
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式