大数据存储域——Kafka设计原理
摘要
本文主要介绍了Kafka的架构原理、消息订阅模式以及在金融风控等领域的应用。Kafka作为数据中转站,可同步不同系统数据,支持事件驱动架构,广泛应用于金融支付与风控场景。其架构包括Producer、Broker、Topic、Partition、Replication、Message、Consumer和Consumer Group等组件,依赖Zookeeper保存元信息。Kafka的消息订阅模式包括点对点、发布-订阅、分区级订阅、静态订阅和动态订阅等,每种模式都有其特点和适用场景。此外,还探讨了Kafka与RocketMQ的对比、消息丢失问题及解决方案、消息存储与删除策略等。
1. Kafka原理介绍
1.1. Kafka基本特点
Kafka 是一个分布式发布-订阅消息系统。是大数据领域消息队列中唯一的王者。最初由 linkedin 公司使用 scala 语言开发,在2010年贡献给了Apache基金会并成为顶级开源项目。至今已有十余年,仍然是大数据领域不可或缺的并且是越来越重要的一个组件。Kafka适合离线和在线消息,消息保留在磁盘上,并在集群内复制以防止数据丢失。kafka构建在zookeeper同步服务之上。它与Flink和Spark有非常好的集成,应用于实时流式数据分析。
Kafka特点:
- 可靠性:具有副本及容错机制。
- 可扩展性:kafka无需停机即可扩展节点及节点上线。
- 持久性:数据存储到磁盘上,持久性保存。
- 性能:kafka具有高吞吐量。达到TB级的数据,也有非常稳定的性能。
- 速度快:顺序写入和零拷贝技术使得kafka延迟控制在毫秒级。
1.2. Kafka架构应用场景
Kafka 的使用场景其实非常广泛,它本质上是一个 高吞吐、可扩展的分布式消息流处理平台,常用于消息队列(MQ)+ 日志系统 + 流式处理 三类场景。结合实际金融支付、风控和互联网场景,我给你梳理下常见的应用:
1.2.1. 消息解耦与削峰填谷
- 场景:上下游系统解耦,生产者只需要把消息写入 Kafka,消费者根据自身处理能力来消费。
- 示例:
- 订单系统下单后,支付系统、库存系统、营销系统都订阅订单消息,异步处理,避免耦合。
- 双十一、618 秒杀场景,流量洪峰先写入 Kafka,消费者按能力消费,防止系统被压垮。
1.2.2. 日志收集与统一处理
- 场景:收集分布式系统中的日志,统一存储、分析。
- 示例:
- 用户行为日志(点击、浏览、下单)发送到 Kafka,再进入 大数据平台(Hadoop、Hive、Spark、Flink) 分析。
- 风控系统采集交易流水日志,实时分析欺诈行为。
1.2.3. 实时流式计算
- 场景:Kafka + Flink / Spark Streaming / Storm,做实时数据处理。
- 示例:
- 实时监控:金融支付中的交易反欺诈系统,实时监控交易特征,秒级拦截可疑交易。
- 实时推荐:电商网站根据 Kafka 中的用户行为数据实时更新推荐结果。
1.2.4. 数据总线(Data Pipeline)
- 场景:作为数据中转站,把不同系统的数据可靠地同步到另一个系统。
- 示例:
- Kafka Connect 将数据库 binlog(MySQL、PostgreSQL) 同步到 ES、HDFS、ClickHouse。
- Kafka MirrorMaker 实现跨数据中心的数据复制(异地多活)。
1.2.5. 事件驱动架构(EDA)
- 场景:通过事件驱动系统协作,而不是强耦合调用。
- 示例:
- 银行风控:用户发起交易 → Kafka 发布事件 → 多个风控模块(黑名单校验、设备指纹校验、额度校验)并行消费。
- 电商业务:下单事件触发 → 物流系统、优惠券系统、积分系统都能订阅同一个事件。
1.2.6. 金融支付与风控场景
这是你熟悉的领域,可以更贴近实际:
- 支付交易流水存储:支付核心系统把交易流水写入 Kafka,用于清算、对账、监管报送。
- 实时风控:交易请求先进入 Kafka,由多个风控服务并行消费,判断风险。
- 账务/清结算:通过 Kafka 进行账务流水的实时同步,避免单点瓶颈。
- 监控告警:Kafka 采集交易异常、延时、错误日志,实时触发告警。
1.3. Kafka架构原理
- Producer:Producer即生产者,消息的产生者,是消息的入口。
- Broker:Broker是kafka实例,每个服务器上有一个或多个kafka的实例,我们姑且认为每个broker对应一台服务器。每个kafka集群内的broker都有一个不重复的编号,如图中的broker-0、broker-1等……
- Topic:消息的主题,可以理解为消息的分类,kafka的数据就保存在topic。在每个broker上都可以创建多个topic。
- Partition:Topic的分区,每个topic可以有多个分区,分区的作用是做负载,提高kafka的吞吐量。同一个topic在不同的分区的数据是不重复的,partition的表现形式就是一个一个的文件夹!
- Replication:每一个分区都有多个副本,副本的作用是做备胎。当主分区(Leader)故障的时候会选择一个备胎(Follower)上位,成为Leader。在kafka中默认副本的最大数量是10个,且副本的数量不能大于Broker的数量,follower和leader绝对是在不同的机器,同一机器对同一个分区也只可能存放一个副本(包括自己)。
- Message:每一条发送的消息主体。
- Consumer:消费者,即消息的消费方,是消息的出口。
- Consumer Group:我们可以将多个消费组组成一个消费者组,在kafka的设计中同一个分区的数据只能被消费者组中的某一个消费者消费。同一个消费者组的消费者可以消费同一个topic的不同分区的数据,这也是为了提高kafka的吞吐量!
- Zookeeper:kafka集群依赖zookeeper来保存集群的的元信息,来保证系统的可用性。
1.4. Kafka 消息订阅模式
Kafka 的消息订阅模式,其实就是 Consumer 如何订阅 Topic 并消费消息 的方式。不同于传统 MQ(例如 RabbitMQ 的交换机 + 路由模式),Kafka的订阅模式比较简洁,但围绕Topic/Partition/ConsumerGroup 有几种典型方式。
模式 | 特点 | 消费次数 | 典型场景 |
点对点(Queue-like) | 组内消息只给一个 Consumer | 每消息 1 次(组内) | 负载均衡任务 |
发布-订阅(Pub/Sub) | 多个组可独立消费 | 每消息 N 次(组数) | 事件通知,多系统订阅 |
分区级订阅 | 精确分区分配策略 | 与分区数量相关 | 顺序消费、流量均衡 |
静态订阅 | 手动指定分区 | 可控 | 用户交易顺序处理 |
动态订阅 | 订阅整个 Topic | 自动分配 | 常见业务消费 |
1.4.1. 点对点(Queue-like 模式)
机制:多个 Consumer 组成一个 Consumer Group;每条消息只会被组内一个 Consumer 消费一次;Partition 会被分配给组内某个 Consumer。类似于传统 MQ 的“队列模式”。
应用场景:下游服务需要做 负载均衡,如支付流水入账任务,多个实例分摊任务量。
点对点模式通常是基于拉取或者轮询的消息传送模型,这个模型的特点是发送到队列的消息被一个且只有一个消费者进行处理。生产者将消息放入消息队列后,由消费者主动的去拉取消息进行消费。点对点模型的的优点是消费者拉取消息的频率可以由自己控制。但是消息队列是否有消息需要消费,在消费者端无法感知,所以在消费者端需要额外的线程去监控。
1.4.2. 发布-订阅(Pub/Sub 模式)
机制:一个Topic可以被多个Consumer Group 订阅;不同组的消费者都能收到完整的消息副本;Broker不会为单个消息维护“已消费状态”,只管存消息。类似于传统 MQ 的“广播模式”。
应用场景:订单事件同时通知 库存系统、营销系统、风控系统。支付交易流水同时写入 清算系统、风控系统、监控系统。
发布订阅模式是一个基于消息送的消息传送模型,发布订阅模型一个消息可以有多种不同的订阅者同时消费。生产者将消息放入消息队列后,队列会将消息推送给订阅过该类消息的消费者。由于是消费者被动接收推送,所以无需感知消息队列是否有待消费的消息!但是consumer1、consumer2、consumer3由于机器性能不一样,所以处理消息的能力也会不一样,但消息队列却无法感知消费者消费的速度!所以推送的速度成了发布订阅模模式的一个问题!假设三个消费者处理速度分别是8M/s、5M/s、2M/s,如果队列推送的速度为5M/s,则consumer3无法承受!如果队列推送的速度为2M/s,则consumer1、consumer2会出现资源的极大浪费!
1.4.3. 分区级订阅(Partition Assignment)
Kafka中,Topic → Partition → Consumer 的绑定方式,有几种策略:
- Range(范围分配)按照分区号顺序分配给消费者(可能导致负载不均)。
- RoundRobin(轮询分配)轮流分配分区,更均匀。
- Sticky(粘性分配,推荐)保证分区尽可能稳定地分配给同一个 Consumer,减少 rebalance 开销。
1.4.4. 静态订阅 vs 动态订阅
- 静态订阅(Assign 模式):Consumer 手动指定订阅的 Topic 和 Partition。适合严格顺序消费的场景,比如一个用户的交易记录必须按顺序处理。
- 动态订阅(Subscribe 模式):Consumer 订阅整个 Topic,具体分区由 Kafka 自动分配。常见于一般业务消费,扩展性更好。
1.4.5. 多播/单播对比
- 单播(Unicast):同一个 Consumer Group 内,消息只会被其中一个 Consumer 消费。
- 多播(Multicast):不同 Consumer Group 都能消费相同的消息,各自独立。
2. kafka实战开发经验总结
2.1. 什么时候使用Kafka、MQ中间件
Kafka和传统消息队列(RabbitMQ、ActiveMQ、RocketMQ 等)虽然都是消息中间件,但定位、架构和适用场景不一样,所以“什么时候用 Kafka、什么时候用 MQ”主要取决于你的业务需求、性能要求和系统架构。
2.1.1. 核心定位对比
特性 | Kafka | MQ(RabbitMQ / RocketMQ / ActiveMQ 等) |
设计目标 | 高吞吐、分布式日志存储、流处理 | 可靠消息传递、业务解耦、事件驱动 |
消费模式 | 发布-订阅(Pub/Sub),支持多消费组,消息可重复消费 | 队列(Queue)+ 发布订阅,通常一次消费即删除 |
消息存储 | 长期存储(按时间/大小保留),可回溯消费 | 消费后一般删除(RocketMQ 可配置保留) |
性能 | 百万级 TPS | 一般万级 TPS(RocketMQ 高性能除外) |
顺序性 | 分区内有序 | 队列内有序 |
事务支持 | 基本事务(Kafka 0.11+),但弱于 MQ | 较完善的事务消息(RocketMQ 事务消息较成熟) |
2.1.2. Kafka、MQ使用场景对比?
场景类型 | 典型需求 | Kafka 适合 | MQ 适合(RabbitMQ / RocketMQ) |
高吞吐日志采集 | 每秒百万级日志/埋点采集,写入存储或分析系统 | ✅(ELK、ClickHouse、Hadoop 等) | ❌(吞吐不够) |
实时流计算 | 需要实时处理数据流,窗口聚合、风控计算 | ✅(Kafka Streams、Flink、Spark Streaming) | ❌(延迟较大) |
大数据中间总线 | 系统间批量传输数据到数据湖、仓库 | ✅ | ❌ |
消息回溯 / 重放 | 消费端需反复读取历史数据 | ✅(可配置保留期) | ❌(消费后删除) |
系统解耦 | 系统间异步调用,降低耦合 | ⚠️(可做但功能简单) | ✅(队列 + 订阅模式) |
事务一致性 | 确保消息与数据库操作同时成功 | ⚠️(Kafka事务弱) | ✅(RocketMQ事务消息成熟) |
延迟/定时任务 | 定时发消息、延迟队列 | ❌ | ✅(RocketMQ 原生延迟,RabbitMQ TTL) |
复杂路由 | 消息根据规则路由到不同队列 | ❌ | ✅(RabbitMQ交换机灵活) |
轻量异步任务 | 低并发的任务调度,如发邮件/短信 | ❌(过重) | ✅(RabbitMQ / RocketMQ) |
多消费者独立消费 | 多个系统独立消费同一份消息 | ✅(多消费组独立) | ⚠️(需额外配置) |
2.1.3. Kafka、MQ选型建议
需求 | 推荐 |
高吞吐(百万级 TPS)、实时流、可回溯 | Kafka |
分布式事务、消息必达 | RocketMQ |
复杂路由、轻量级消息通信 | RabbitMQ |
实时流 + 事务一致性 | Kafka + 事务补偿 / RocketMQ |
大数据日志采集 | Kafka |
延迟消息 / 定时任务 | RocketMQ / RabbitMQ(TTL) |
2.2. 点对点(Queue-like 模式) 中消息是否标记为已经消费?
在 Kafka 的点对点模式(Consumer Group 内单消费)下:消息不会被标记已消费;Broker 只管存消息,消费状态由 Consumer Group 的 offset 记录;
2.2.1. Kafka的设计和传统 MQ 不一样
在 RabbitMQ、ActiveMQ 等传统 MQ 里,Broker 会记录消息是否已经被某个消费者确认(ACK),已消费的消息就会被标记或删除。但在 Kafka 中:Broker 不关心消费状态,只负责存储消息。Consumer 自己维护消费进度(offset)。
2.2.2. 消费进度的管理方式
每个Consumer Group在Kafka里会有一个 offset,指向该组在某个 Partition 中的消费进度(即下一个要消费的消息位置)。这个 offset 默认存储在 __consumer_offsets
内置主题中,而不是存在消息本身。所以消息不会有“已消费标记”,只是有“消费到哪里了”的进度信息。
2.2.3. 结果表现
消息被消费后,仍然会在 Broker 的日志文件里保留(直到达到过期时间或超过配置的存储大小才删除)。如果 Consumer 提交 offset 出错:可能会 重复消费(offset 没提交成功,下次还会读到同一条消息)。或者消息丢失(offset 提交过早,但实际还没处理完)。
2.3. Kafka的消息保证不重复消费是依赖于消费者来实现?
Kafka本身的 设计哲学就是:Broker 只负责存储和分发消息,不保证“只消费一次”,消息是否会被重复消费,要靠 Consumer(消费者端)自己来保证。
2.3.1. 为什么Kafka不能天然保证不重复消费?
- Offset提交机制决定的
- 消费者在拉取消息后,通常会在处理完成后提交offset。
- 如果处理成功但 offset 提交失败 → 下次重启会从旧 offset 继续消费 → 重复消费。
- 如果offset 提交成功但处理失败 → 消息“跳过”了 → 消息丢失。
- Kafka Broker 不维护消费状态
- Broker不记录某个消息是否已被消费。它只维护partition 的日志文件 + 保留策略。
2.3.2. Kafka 官方给出的消费语义有三种:
- At most once(至多一次):offset 先提交,再处理消息。风险:消息可能丢失,但绝不会重复。
- At least once(至少一次)(默认):先处理消息,再提交 offset。风险:可能重复消费,但不会丢
- Exactly once(恰好一次):Kafka 0.11 引入的特性,需要 幂等 Producer + 事务性写入 配合,在特定场景(Kafka → Kafka,Kafka → 支持事务的存储系统)可以实现。对 Consumer 来说,仍需保证幂等(例如业务处理可重试)。
2.3.3. 费端保证不重复消费的常见手段
- 幂等性处理:业务层面保证,即使同一条消息被重复处理,结果也是一样。例如:数据库
INSERT ... ON DUPLICATE KEY UPDATE
,支付扣款时先判断“订单是否已扣款”。 - 唯一键去重:给每条消息一个全局唯一 ID(如订单号、流水号、事务 ID)。Consumer 处理前先检查该 ID 是否已经处理过。
- 事务:Kafka 提供
read-process-write
的事务性 API,确保消费和结果写入要么都成功,要么都失败。
2.4. 金融风控场景中什么场景使用kafka什么时候使用RocketMQ?
2.4.1. 金融风控中Kafka的典型使用场景
Kafka 的强项是 高吞吐、低延迟、可回溯的流数据处理,在风控里更多用于 数据实时流转和大数据计算。总结:Kafka 在风控中是 数据总线,负责把交易、埋点、外部数据等实时送到决策引擎、大数据平台,强调吞吐、实时、可回溯。
场景 | 说明 | 为什么用 Kafka |
实时交易数据流 | 实时获取交易流水、支付请求、账户变动信息,推送到风控计算引擎 | 高吞吐(百万级 TPS),延迟低,可多消费组并行处理 |
用户行为埋点 & 反欺诈 | 埋点收集登录、设备指纹、IP 地址、地理位置、操作行为等 | 可与 Flink / Spark Streaming 做实时风险评分 |
外部数据接入 | 接入黑名单、征信、第三方风控数据,汇聚到数据湖 | 数据量大,可持久化保存并回放 |
实时监控 & 预警 | 将风控决策事件推送到监控系统(如 Grafana、Prometheus) | 多消费者订阅同一数据流,不互相影响 |
批量模型重算 / 回测 | 保存历史交易数据,用于风控模型回测和调优 | Kafka 可按时间窗口读取历史消息 |
2.4.2. 金融风控中RocketMQ的典型使用场景
RocketMQ在风控里更多用于可靠事务消息、事件驱动和任务调度,强调可靠性和一致性。
场景 | 说明 | 为什么用 RocketMQ |
风控决策结果落库 | 决策结果(放行、拦截、人工复核)与交易落库保持事务一致 | 事务消息保证数据库与消息一致提交 |
反欺诈补救动作 | 如冻结账户、拦截支付、通知客服,这些动作必须可靠执行 | 消息必达,支持重试机制 |
延迟风控处理 | 某些规则需要延迟检查(如 24 小时内重复提现) | 原生延迟消息支持 |
风控事件驱动 | 触发下游系统(反洗钱 AML 系统、合规审核)执行任务 | 可靠投递保证事件不会丢失 |
批量风险任务调度 | 夜间批量跑风控规则、信用评估任务 | 结合延迟消息 / 定时任务执行 |
2.4.3. 对比总结表
需求 | Kafka | RocketMQ |
高吞吐交易流采集 | ✅ | ❌ |
用户行为埋点 | ✅ | ❌ |
实时流计算(Flink) | ✅ | ❌ |
历史数据回放 | ✅ | ❌ |
决策结果落库事务一致 | ❌ | ✅ |
延迟/定时风控规则 | ❌ | ✅ |
风控处置任务必达 | ⚠️(需自己实现) | ✅ |
异步事件驱动 | ⚠️ | ✅ |
2.5. kafka消息可能丢失呢?
是的,Kafka 消息是可能丢失的,而且丢失的原因和环节都比较多,尤其在金融风控这种高可靠场景下,需要特别注意。
2.5.1. 生产端(Producer)丢消息的原因
原因 | 说明 | 解决方式 |
acks=0 / acks=1 | - - | 生产环境用 |
未处理发送异常 | 发送时网络异常/超时,应用没做 | 对 send() 结果做异常处理或重试 |
未开启重试机制 | Producer 默认 retries=0,发送失败就丢 | 设置 |
缓冲区未刷盘 | Producer 进程挂了,缓冲区里的数据没发出去 | 开启 |
2.5.2. Kafka Broker丢消息的原因
原因 | 说明 | 解决方式 |
ISR(同步副本集合)不完整 | Leader 崩溃时,Follower 未完全同步数据,被选为 Leader 后数据丢 | 开启 (比如 2),配合 |
日志未落盘 | Kafka 默认批量刷盘( | 关键主题可调低刷盘间隔(会降低性能) |
unclean.leader.election.enable=true | 允许不完整副本选为 Leader,会导致数据丢失 | 生产环境设为 |
磁盘损坏/数据目录丢失 | 物理损坏、磁盘满等异常 | 监控磁盘、配 RAID / 云存储快照 |
2.5.3. 消费端(Consumer)丢消息的原因
原因 | 说明 | 解决方式 |
自动提交 offset(enable.auto.commit=true) | Kafka 默认 5s 自动提交 offset,如果消费未完成就宕机,下次会跳过未处理的消息 | 改为手动提交 offset(commitSync/commitAsync)在处理完成后提交 |
先提交 offset 后处理 | 提交成功但处理失败,消息丢失 | 先处理,再提交 offset |
批量消费未处理完 | 一批数据部分成功部分失败,但 offset 已提交到最大位置 | 使用精细化 offset 控制或单条提交 |
消费端丢弃异常消息 | 消费代码中 try-catch 后直接忽略异常数据 | 对异常消息入死信队列(DLQ) |
2.5.4. Kafka 可靠性最佳实践(防止丢消息)
如果你在金融风控系统中用 Kafka,建议这样配置:
2.5.4.1. 生产端(Producer)
acks=all
retries=Integer.MAX_VALUE
enable.idempotence=true
max.in.flight.requests.per.connection=1
2.5.4.2. Broker
min.insync.replicas=2
unclean.leader.election.enable=false
2.5.4.3. 消费端(Consumer)
enable.auto.commit=false
# 业务处理完成后手动 commit
并且:
- 对关键消息使用 多副本 + 事务消息(Kafka 0.11+)
- 对异常消息使用 死信队列(Dead Letter Queue)
- 定期做 Kafka 数据校验和对账
2.6. kafka 只负责存储消息,消息存储后怎么删除?
Kafka 的设计理念就是 Broker 只负责存储消息,不管消费状态,所以消息是否被“消费”跟消息删除没有关系。Kafka 删除消息主要依赖 保留策略(Retention Policy) 来决定。Kafka 的消息删除机制与消费无关,主要靠 保留策略 来实现:
- 基于时间(默认 7 天)
- 基于大小(超过磁盘上限)
- 按 segment 文件批量删除(不是逐条删除)
- 日志压缩(compact):保留每个 key 的最新值
👉 所以 Kafka 可以保证消息存储高效,同时也能灵活应对“短期日志存储”和“长期状态存储”两类需求。
2.6.1. 基于时间的保留(Log Retention by Time)
- 配置:
log.retention.hours
(默认 168 小时 = 7 天)也可以用log.retention.minutes
或log.retention.ms
精确控制 - 含义:超过指定时间的消息会被删除(底层是删除旧的 segment 文件)。
- 应用场景:日志采集场景,保留最近 7 天的日志即可。
2.6.2. 基于大小的保留(Log Retention by Size)
- 配置:
log.retention.bytes
,每个 partition 保留的最大数据量。 - 含义:超过大小后,旧数据被删除。
- 应用场景:磁盘资源有限时,通过大小控制日志量。
2.6.3. 基于日志段(Log Segment)的删除
- Kafka 底层不是一条条删除,而是按 segment 文件 删除:每个 partition 的日志会被切分成多个 segment 文件(默认 1GB 或
log.segment.bytes
配置)。当一个 segment 文件中的数据都过期后,整个文件会被删除。 - 好处:批量删除,效率高。避免逐条消息管理,保持简单高效。
2.6.4. 基于日志压缩(Log Compaction)
除了“保留多久/多大”,Kafka 还支持 Log Compaction(日志压缩):
- 配置:
cleanup.policy=compact
- 含义:只保留每个 key 的最新一条记录,旧的记录会被清理。
- 应用场景:适合保存状态类数据,比如“用户的最新余额”“账户状态”。
对比:
delete
策略:过期消息整体删除(常用)。compact
策略:按 key 只保留最新值(状态存储用)。- 也可以同时启用:
cleanup.policy=delete,compact
。
2.6.5. 手动删除(不常用)
- 可以通过 管理工具 删除整个 Topic 或 Partition:
kafka-topics.sh --delete --topic test --zookeeper zk1:2181
- 或者修改 Topic 配置,强制缩短保留时间,然后触发清理。
2.7. RockMQ存在消息丢失的情况吗?
是的,RocketMQ 也可能丢消息,只是它的设计在可靠性上比 Kafka强一些(尤其是事务消息、消息必达机制),但是如果配置不当或使用不正确,依然会丢。
2.7.1. 生产端(Producer)丢消息的原因
RocketMQ 的 Producer 如果配置不合理,丢消息概率也不低。
原因 | 说明 | 解决方式 |
发送失败未重试 | 网络抖动、Broker 不可用时,Producer | 开启 |
单向发送(Oneway) |
| 金融风控禁止用单向发送 |
异步发送没回调检查 | 异步模式下 | 异步发送要有失败重试逻辑 |
事务消息回查失败 | 事务半消息未提交且回查失败,消息被丢弃 | 保证事务回查逻辑可用且幂等 |
2.7.2. Broker(服务端)丢消息的原因
RocketMQ 在 Broker 存储阶段,如果磁盘或刷盘机制配置不当,也可能丢。
原因 | 说明 | 解决方式 |
刷盘方式为异步(ASYNC_FLUSH) | Broker 先写内存,稍后刷盘,宕机前的数据会丢 | 对金融交易/风控类主题用 |
主从复制为异步(ASYNC_MASTER) | 主节点宕机时,未同步到从节点的数据丢失 | 金融场景用 (同步双写) |
磁盘损坏 | 物理损坏、文件系统异常 | RAID、云存储快照、磁盘监控 |
Broker 崩溃导致 CommitLog 未落盘 | 异步刷盘下,崩溃会丢部分消息 | 关键业务同步刷盘 |
2.7.3. 消费端(Consumer)丢消息的原因
RocketMQ 消费模式是“至少一次”(At-Least-Once),所以不会无故丢消息,但如果 offset 管理不当,也会丢。
原因 | 说明 | 解决方式 |
自动提交 offset | 消费端默认自动提交,如果提交后处理失败,消息会被“认为已消费”而丢失 | 金融场景用手动 ACK,处理完成后再提交 |
消费失败但未重试 | 默认会重试,但如果配置了 | 确保开启重试,并配置死信队列(DLQ) |
顺序消息消费阻塞 | 顺序消费模式下,长时间阻塞会导致 Broker 认为消费成功 | 在业务超时前主动返回 RECONSUME_LATER |
消息被丢到 DLQ | 重试超过限制次数会进入死信队列,不被正常消费 | 定期扫描并人工处理 DLQ |
2.7.4. RocketMQ 防丢最佳实践(金融风控场景)
如果是风控、交易、清算等强一致性场景,建议这样配置:
2.7.4.1. 生产端
producer.setRetryTimesWhenSendFailed(5);
producer.setRetryTimesWhenSendAsyncFailed(5);
# 避免 Oneway 模式
2.7.4.2. Broker
properties复制编辑
flushDiskType=SYNC_FLUSH
brokerRole=SYNC_MASTER
2.7.4.3. 消费端
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
consumer.setMaxReconsumeTimes(16); // 超过进入DLQ
consumer.setMessageModel(MessageModel.CLUSTERING); // 集群模式
并且:
- 对异常消息统一进入死信队列(DLQ),人工或自动补偿。
- 对事务消息保证回查逻辑幂等且可用。
- 对关键主题开启消息轨迹(Message Trace)方便排查丢失点。
2.8. RocketMQ 中记录消息被消费吗?
2.8.1. RocketMQ与Kafka的区别
- Kafka:Broker 不关心消息是否消费,只存消息;offset 在 Consumer Group 端维护,存储在
__consumer_offsets
主题中;所以消息不会有“消费标记”。 - RocketMQ:Broker 会记录消费进度(消费队列 + 消费进度);通过 ConsumeQueue + Offset 机制来追踪消息是否被消费。
2.8.2. RocketMQ 的消费队列(ConsumeQueue)
RocketMQ 的存储模型:
- CommitLog:存储所有原始消息(顺序写入,类似 Kafka 的 log)。
- ConsumeQueue:存储消费队列(索引文件),记录消息在 CommitLog 中的位置 + 消息 Tag 等。
- ConsumerOffset:存储消费者组消费到哪条消息。
所以 RocketMQ Broker 能知道:某个消息被投递给哪些消费者组。某个消费者组消费到了哪条消息(offset)。
2.8.3. RocketMQ消费确认(ACK)
消息队列 | offset 存储位置 | 维度 | 说明 |
Kafka | Broker 内部主题 | Consumer Group | 由消费者提交,Kafka 统一管理 |
RocketMQ (集群模式) | Broker (ConsumerOffsetManager) | Consumer Group + Queue | 由 Broker 统一管理 |
RocketMQ (广播模式) | Consumer 本地文件 | Consumer 实例 | 每个消费者独立维护 |
- RocketMQ 默认是“至少一次(At least once)”语义:Consumer 拉取消息 → 成功处理 → 返回 ACK 给 Broker;Broker更新该 Consumer Group的offset。
- 如果处理失败:消息会被重新投递(支持延时重试 / 死信队列 DLQ)。
这意味着RocketMQ确实在 Broker层面知道一条消息有没有被消费确认。
2.8.4. RocketMQ消息删除
- 与 Kafka 类似,RocketMQ 也不会因为“消息被消费”就删除消息。
- 消息删除仍然依赖 过期时间(默认 3 天,可配置
fileReservedTime
)。 - 所以历史消息依然会在磁盘上保留一段时间,方便回溯。
2.9. 防止消息丢失解决方案有哪些?
防止消息丢失的方案可以分为 端到端三个环节:生产端 → 消息中间件 → 消费端。不同环节丢失的原因不一样,解决方案也不同。
2.9.1. 1️⃣ 生产端防丢
方法 | 作用 | 适用 MQ |
可靠 ACK 机制( | 确保消息写入多个副本后才算成功 | Kafka / RocketMQ |
幂等发送( | 避免重试导致重复数据 | Kafka |
发送异常处理与重试 | 网络异常、超时必须重发 | 所有 MQ |
事务消息 | 与数据库操作原子性一致 | Kafka / RocketMQ |
Outbox + CDC | 数据库先落地消息,再异步投递 MQ | 所有 MQ |
批量 flush 控制 | 防止 Producer 崩溃时缓冲区数据丢失 | Kafka / RocketMQ |
2.9.2. 2️⃣ 消息中间件防丢
方法 | 作用 | MQ 特性 |
多副本存储( | 避免单点故障丢消息 | Kafka / RocketMQ |
同步刷盘( | 避免 Broker 崩溃时丢内存数据 | RocketMQ |
ISR 同步策略( | 仅在副本足够时才确认消息 | Kafka |
禁止 Unclean Leader 选举 | 避免落后副本当 Leader 导致丢数据 | Kafka |
磁盘健康监控 | 防止物理故障丢消息 | 所有 MQ |
延迟队列 + DLQ | 遇到处理异常可暂存或丢到死信队列 | 所有 MQ |
2.9.3. 3️⃣ 消费端防丢
方法 | 作用 | MQ 特性 |
手动 ACK / 手动提交 offset | 确保消费成功后才确认 | Kafka / RocketMQ |
先处理,再提交 offset | 防止处理失败但 offset 已提交 | Kafka |
消费重试机制 | 消费失败可重新投递 | 所有 MQ |
死信队列(DLQ) | 多次失败的消息单独存储 | 所有 MQ |
幂等消费 | 防止重试导致重复处理 | 所有 MQ |
消费进度持久化 | 确保宕机恢复后能从上次位置继续 | Kafka / RocketMQ |
2.9.4. 4️⃣ 端到端防丢组合方案
根据业务重要性选择:
- 高一致性(金融交易、风控决策)
- RocketMQ 事务消息 / Kafka 事务 + 幂等发送
- 消费端手动 ACK + 幂等消费
- Broker 多副本 + 同步刷盘
- 高吞吐实时流(埋点、日志分析)
- Kafka
acks=all
+ ISR 策略 - 消费端自动提交 offset + 异步批量处理(可容忍极少丢失)
- Kafka
- 可补偿业务(异步任务、通知)
- 先写数据库 + 定时补偿任务
- 或 Outbox + CDC
一句话总结:防丢必须从生产端可靠发送、MQ 高可用存储、消费端可靠确认 三方面同时保障,单靠“先写数据库再发 MQ”并不是唯一解。
2.10. Outbox + CDC 是什么方案?
Outbox + CDC 是一种业界常用的防止消息丢失 & 保证数据库与消息一致性的架构模式,尤其在金融、支付、风控等对一致性要求高的系统里很常见。
2.10.1. 为什么会有 Outbox + CDC
在普通架构里,常见有两种做法:
- 先写数据库,再发 MQ → 如果发 MQ 失败,需要补偿逻辑,否则不一致。
- 先发MQ,再写数据库 → 如果写库失败,就会产生无效消息。
这两个都不是完美方案,尤其金融风控场景下,数据和消息必须100%一致。于是出现了 Outbox(发件箱)模式 + CDC(变更数据捕获) 的组合。
2.10.2. Outbox 模式
核心思想:
- 在业务数据库中增加一个消息表(outbox table)
- 在同一个本地事务中同时写入:
- 业务数据(比如风控结果、交易流水)
- 消息数据(写入消息表)
这样,业务数据和消息数据要么一起成功,要么一起失败,天然一致。
例子:
BEGIN;
INSERT INTO risk_decision (id, user_id, decision) VALUES (123, 456, 'REJECT');
INSERT INTO outbox (id, topic, payload, status) VALUES (999, 'risk_event', '{...}', 'NEW');
COMMIT;
2.10.3. CDC(Change Data Capture)
核心思想:
- 用一个独立的消息发送服务(或中间件)实时监听 outbox 表变化
- 通过读取数据库 binlog(如 MySQL binlog、PostgreSQL WAL)来捕获新插入的消息
- 把消息发送到 MQ(Kafka / RocketMQ)
- 发送成功后更新 outbox 表的状态(比如
status=PROCESSED
)
常用的 CDC 工具:
- Debezium(最常用,支持 Kafka、Pulsar、RabbitMQ)
- Canal(阿里开源)
- Maxwell
2.10.4. 方案优缺点
优点 | 缺点 |
强一致:数据库和 MQ 消息一定一致 | 增加一张 outbox 表,额外占用数据库存储 |
无分布式事务:只用本地事务 | 需要部署 CDC 服务,运维复杂度增加 |
可重放:消息表是天然的“重发记录” | 消息延迟略高(取决于 CDC 频率) |
可审计:可以追踪每条消息的生命周期 | 适合异步业务,不适合极低延迟场景 |
2.10.5. 在金融风控中的典型用法
- 风控结果写库 + 发消息:确保风控决策落库成功才发消息给下游(冻结、人工复核、黑名单同步等)
- 交易流水落库 + 事件广播:先保证交易落库,再异步广播到风控、账务、营销等系统
- 反欺诈事件记录 + 数据分析:确保反欺诈事件既能写库留痕,也能推送到 Kafka 做实时分析
2.10.6. 流程图
[业务服务]| 本地事务写两份数据|----------------------------| 业务表 + Outbox 消息表↓
[数据库]↓ CDC监听 binlog
[CDC 服务 (Debezium/Canal)]↓
[MQ (Kafka/RocketMQ)]↓
[下游消费者]
💡 总结
Outbox + CDC 本质是:
- Outbox:保证业务数据和消息的原子性
- CDC:负责异步、可靠地把消息送到 MQ
这样既避免了分布式事务的复杂性,又能保证数据与消息强一致,是金融风控里非常稳妥的方案。