消息积压
产生消息积压的原因
常见造成消息积压的原因:
- 生产者流量徒增
- 消费者机器故障导致消息消费能力下降
- 单条消息处理耗时过长
- 消费线程配置不合理
解决方式
生产端限流
不建议
增加消费端能力
增加消费端能力的方式:
- 能加消费者数量(在kafka下没有效果)、增加消费线程
- 检测单条消息消费时长、如果太长看是否有调优手段进行优化
- 单条消息消息改成批量消费,如果涉及数据库插入,可以做该优化
- 异步话,如果消息消费涉及三方调用,而且该调用耗时比较长,可以考虑异步调用
- 缓存预热,如果涉及很多信息查询,可以先做缓存预热
- 考虑是否关闭消费失败重试
消息分级管控
对非核心的消息进行降级、延迟处理,先持久化到数据库中,后续处理.
死信队列兜底
消息重试失败时,自动转移,避免阻塞主流程.
消息监测和预警
监控消息指标:
- 消费者生产速率
- 消费速率
- 堆积量
- 延迟消费
故障演练
日常故障演练,提前发现问题:
- 每月消息量模拟
- 预警线设置,如资源使用率
- 一系列开关设置,可以迅速切断一些非重要消息入口或者消费逻辑
- 核心业务和非核心业务隔离
如何保证消息不丢失
一、Kafka 保证消息不丢失的机制
1. 生产端:确保消息成功发送
- acks 参数配置
通过设置
acks参数控制 Producer 对 Broker 响应的要求,避免消息发送阶段丢失:acks=0:Producer 不等待 Broker 响应,直接认为发送成功,可能因网络问题丢失消息。acks=1(默认):Broker 的 Leader 节点接收消息后响应,若 Follower 未同步时 Leader 宕机,可能丢失消息。acks=-1/all:Leader 等待所有 ISR(In-Sync Replicas)中的 Follower 同步消息后响应,可靠性最高。
- 重试机制
当发送失败(如网络异常、Broker 宕机)时,Producer 自动重试(通过
retries参数配置次数),避免因瞬时故障导致消息丢失。 - 幂等性与事务
- 幂等性:开启
enable.idempotence=true后,Producer 通过 PID(Producer ID)和 Sequence Number 确保重复发送时消息唯一,避免重复写入。 - 事务:通过
transactional.id配置事务型 Producer,支持 “发送 - 提交 / 回滚” 原子操作,确保跨分区消息的一致性(如多分区发送失败时回滚)。
- 幂等性:开启
2. 存储端:确保消息持久化与副本同步
- 分区与副本机制
Kafka 通过多副本(Replica)机制保障消息冗余:
- 每个分区有一个 Leader 和多个 Follower,消息先写入 Leader,再同步到 Follower。
- ISR 列表维护 “与 Leader 同步的 Follower”,只有 ISR 中的副本才被认为可用,避免从落后的 Follower 恢复数据。
- 持久化配置
- 消息写入磁盘时,通过
log.flush.interval.messages和log.flush.interval.ms控制刷盘时机,或设置fsync策略(如sync.frequeency=1表示每写入 1 条消息强制刷盘)。 - 分区日志分段存储,即使部分数据损坏也可通过副本恢复。
- 消息写入磁盘时,通过
- 故障转移机制 当 Leader 宕机时,Kafka 通过 Controller 选举新 Leader,且仅从 ISR 中选择,确保新 Leader 包含最新消息,避免数据丢失(除非所有 ISR 副本都宕机,此时可能丢失未同步的消息)。
3. 消费端:确保消息被正确消费
- 位移提交机制
- 消费者通过
auto.commit.offset控制自动提交位移的频率,若设置为false,需手动提交(如处理完消息后提交),避免位移提前提交导致重启后重复消费或丢失未处理的消息。 - 位移存储在
__consumer_offsets主题中,通过多副本保障位移数据不丢失。
- 消费者通过
- 重试与死信队列 消费者处理消息失败时,可通过业务逻辑实现重试(如存入重试 Topic),或发送到死信队列(需自定义实现),避免因处理异常导致消息被丢弃。
二、RocketMQ 保证消息不丢失的机制
1. 生产端:可靠发送与事务支持
- 发送模式
- 同步发送:Producer 等待 Broker 响应,确保消息写入成功,适用于可靠性要求高的场景。
- 异步发送:通过回调函数处理发送结果,失败时可重试。
- 单向发送:不等待响应,可能丢失消息,适用于允许少量丢失的场景(如日志)。
- 事务消息
RocketMQ 支持两阶段事务(Half Message + 事务状态回查),确保本地事务与消息发送的一致性:
- 发送 Half Message 到 Broker(不可见)。
- 执行本地事务。
- 根据事务结果提交或回滚 Half Message(提交后消息可见)。 若 Producer 宕机,Broker 通过回查机制确认事务状态,避免消息丢失。
- 定时重试与批量发送
- 发送失败时自动重试(可配置重试次数和间隔)。
- 批量发送时若部分消息失败,支持重试或跳过,避免整个批次丢失。
2. 存储端:刷盘与主从复制
- 刷盘机制
- 异步刷盘(默认):消息写入内存映射文件(PageCache)后即响应,由后台线程定期刷盘,性能高但可能丢失少量未刷盘的消息。
- 同步刷盘:消息写入 PageCache 并刷盘到磁盘后才响应,可靠性高但性能略低。
- 主从复制与 HA 机制
- RocketMQ 通过主从架构(Master-Slave)实现数据冗余,Master 负责读写,Slave 从 Master 同步数据。
- HA(High Availability)机制:Slave 主动从 Master 拉取数据,或 Master 推送给 Slave,确保数据同步。
- 可配置
brokerRole为SYNC_MASTER(同步模式,Slave 同步后才响应)或ASYNC_MASTER(异步模式),平衡性能与可靠性。
- CommitLog 与 ConsumeQueue
- 消息存储在 CommitLog(全局有序),ConsumeQueue(分区逻辑队列)记录消息偏移量,即使 ConsumeQueue 损坏也可通过 CommitLog 重建,避免消费偏移量丢失。
3. 消费端:精确消费与重试机制
- 消费模式与偏移量管理
- 集群消费:多个消费者实例分摊消费,偏移量由 Broker 维护(存在 ConsumeQueue 中),避免客户端本地存储导致的偏移量丢失。
- 广播消费:每个消费者消费全量消息,偏移量存储在客户端,需确保本地持久化。
- 重试队列与死信队列
- 消费失败时,消息自动进入重试队列(默认 16 次重试,每次间隔递增),重试失败后进入死信队列(DLQ,每个 Topic 对应一个 % DLQ% 后缀的死信 Topic),避免消息被直接丢弃。
- 可通过配置
maxReconsumeTimes控制重试次数,死信队列中的消息可手动处理。
- 拉取与推送模式
- 拉取模式(Pull):消费者主动拉取消息,处理完后才更新偏移量,避免未处理的消息被标记为已消费。
- 推送模式(Push,基于长轮询实现):Broker 主动推送消息,但消费者可通过流控机制(如暂停拉取)控制消费节奏,防止过载导致消息处理失败。
三、两者对比与核心差异
| 维度 | Kafka | RocketMQ |
|---|---|---|
| 生产端事务 | 基于 Producer 事务,支持跨分区事务 | 两阶段事务消息,支持本地事务回查 |
| 存储可靠性 | 依赖 ISR 副本同步,异步刷盘为主 | 支持同步 / 异步刷盘,主从复制更灵活 |
| 消费端重试 | 需自定义重试逻辑(如重试 Topic) | 内置重试队列和死信队列 |
| 偏移量管理 | 存储在系统 Topic,自动 / 手动提交 | 集群消费由 Broker 管理,广播消费存本地 |
| 数据丢失风险点 | ISR 副本全宕机、位移提前提交 | 同步刷盘未开启、主从同步延迟 |
四、最佳实践:如何最大化避免消息丢失
Kafka 配置建议
- 生产端:
acks=-1+retries=MAX+ 开启幂等性或事务。 - 存储端:设置合理的 ISR 副本数(如 3 副本)+ 同步刷盘(按需)。
- 消费端:
auto.commit.offset=false+ 手动提交位移(处理完成后)。
RocketMQ 配置建议
- 生产端:使用同步发送 + 事务消息(若有一致性需求)。
- 存储端:
brokerRole=SYNC_MASTER+flushDiskType=SYNC_FLUSH。 - 消费端:设置合适的
maxReconsumeTimes+ 定期处理死信队列。
重复消费
我理解解决重复消费主要从两个方面:
- 生产端生产消息设置正确的唯一ID,如果设置了正确的唯一ID,那么服务端开启幂等性的操作就可有可无
- 消费端做好幂等消费消息的处理,方式如下
- 加锁: 对合适的维度如消息的唯一id加锁
- 数据库:先入库,通过数据库的唯一键保证,消息不会被重复消息
- 状态机:只处理数据当前状态之后的数据
- 手动提交,这块主要是应对批量消费和多线程消费
如何保证顺序消息
kafka和rocketmq的区别
Kafka 通过分区(Partition)内有序实现顺序消费,但跨分区无法保证全局顺序。
RocketMQ 通过队列(Queue)锁 + 单线程消费实现严格顺序,支持局部顺序和全局顺序.
rocketmq队列级顺序消费
- 生产者发送:
同一业务 ID 的消息通过
MessageQueueSelector强制路由到同一队列 - 消费者配置:
使用
MessageListenerOrderly接口,RocketMQ 自动为每个队列加锁,确保单线程消费。
全局顺序消费
- 实现方式: 将 Topic 的队列数设置为 1,生产者和消费者均配置为单实例。此时所有消息进入唯一队列,由单线程消费,实现全局顺序。
- 配置示例
# Broker配置
defaultTopicQueueNums=1 # 全局顺序时设为1
# 生产者配置
producer.setDefaultTopicQueueNums(1);
# 消费者配置(单实例启动)
锁机制与性能优化
- 队列锁:RocketMQ 为每个队列维护一把锁(
MessageQueueLock),消费前需获取锁。 - 性能优化
- 可通过增加队列数提高并行度(如 100 个队列,每个队列单线程消费)。
- 顺序消费时,吞吐量约为并发消费的 1/3(官方数据)。
最佳实践建议
1. Kafka 场景
-
若需严格顺序,将分区数设为 1,且单线程消费。
-
按业务 Key(如订单 ID)路由到固定分区,牺牲并行度换顺序。
-
示例:
// 生产者配置 props.put("partitioner.class", "com.example.OrderPartitioner"); // 消费者配置 props.put("max.poll.records", 1); // 每次只拉取1条,确保顺序
2. RocketMQ 场景
-
使用
MessageListenerOrderly实现队列级顺序。 -
全局顺序时,将队列数设为 1,且控制生产者 / 消费者实例数为 1。
-
性能优化:
// 增加消费线程数(但单队列仍单线程) consumer.setConsumeThreadMin(20); consumer.setConsumeThreadMax(20);
顺序消费的局限性
- 吞吐量瓶颈:顺序消费必然导致单线程处理,无法充分利用多核 CPU。
- 故障恢复延迟:若消费线程崩溃,需等待重启后才能继续处理后续消息。
- 适用场景有限:仅适用于强依赖消息顺序的业务(如订单状态流转),多数场景可通过业务层幂等性降低对顺序的依赖。
事务消息
Kafka 与 RocketMQ 事务消息原理对比
一、核心设计目标
| 维度 | Kafka | RocketMQ |
|---|---|---|
| 事务定义 | 跨分区 / 主题的原子性写入 | 本地事务与消息发送的原子性绑定 |
| 适用场景 | 流处理中的 Exactly Once 语义 | 分布式事务(如订单支付后发送通知) |
| 一致性级别 | 保证 Producer 写入的原子性 | 保证本地事务与消息状态的一致性 |
二、Kafka 事务消息原理
Kafka 的事务消息主要用于实现跨分区 / 主题的 Exactly Once 语义,核心机制如下:
1. 事务管理器(Transaction Coordinator)
- 角色:每个 Broker 节点都有一个 Transaction Coordinator,负责管理事务状态。
- 事务元数据存储:事务状态存储在
__transaction_state主题中,默认 50 个分区。
2. 事务 ID(Transaction ID)
- 作用:唯一标识一个事务,需在 Producer 端配置
transactional.id。 - 幂等性保障:相同 Transaction ID 的 Producer 重启后,Kafka 能识别并清理未完成的旧事务。
3. 两阶段提交(2PC)流程
1. Producer向Coordinator注册事务
2. Producer开始事务 → 发送消息(半消息,不可见)
3. Producer提交本地事务
4. 若成功:Coordinator发送COMMIT → 消息变为可见
若失败:Coordinator发送ABORT → 丢弃半消息
4. 关键代码示例
// 配置事务ID和幂等性
props.put("transactional.id", "my-transactional-id");
props.put("enable.idempotence", true);
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
// 初始化事务
producer.initTransactions();
try {
// 开启事务
producer.beginTransaction();
// 发送消息到多个分区/主题
producer.send(new ProducerRecord<>("topic1", "key", "value1"));
producer.send(new ProducerRecord<>("topic2", "key", "value2"));
// 执行本地事务(如更新数据库)
executeLocalTransaction();
// 提交事务
producer.commitTransaction();
} catch (Exception e) {
// 回滚事务
producer.abortTransaction();
}
三、RocketMQ 事务消息原理
RocketMQ 的事务消息主要用于本地事务与消息发送的原子性绑定,核心机制如下:
1. 半消息(Half Message)
- 定义:Producer 先发送到 Broker 但对 Consumer 不可见的消息。
- 存储机制:半消息存储在 CommitLog 中,但 Consumer 无法消费,直到状态确认。
2. 事务状态回查
- 触发条件:若 Broker 长时间未收到 Producer 的事务状态(COMMIT/ROLLBACK),主动回查。
- 实现方式:Producer 需实现
LocalTransactionStateChecker接口,处理回查请求。
3. 两阶段提交流程
1. Producer发送半消息到Broker
2. Broker存储半消息,返回ACK
3. Producer执行本地事务
4. Producer根据事务结果发送COMMIT/ROLLBACK到Broker
5. 若Broker未收到状态:
- 定时回查Producer的本地事务状态
- Producer根据本地事务状态返回COMMIT/ROLLBACK
4. 关键代码示例
TransactionMQProducer producer = new TransactionMQProducer("producerGroup");
producer.setNamesrvAddr("namesrv:9876");
// 注册事务监听器
producer.setTransactionListener(new TransactionListener() {
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
// 执行本地事务(如数据库操作)
try {
executeLocalDBTransaction();
return LocalTransactionState.COMMIT_MESSAGE;
} catch (Exception e) {
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
// 事务状态回查:查询本地事务状态
return queryLocalTransactionState(msg);
}
});
producer.start();
// 发送事务消息
Message msg = new Message("topic", "tag", "content".getBytes());
SendResult sendResult = producer.sendMessageInTransaction(msg, null);
四、核心差异对比表
| 维度 | Kafka | RocketMQ |
|---|---|---|
| 事务协调者 | 依赖 Broker 内置的 Transaction Coordinator | 依赖 NameServer 和 Broker 协同处理 |
| 状态存储 | 存储在__transaction_state主题中 | 存储在 Broker 的 CommitLog 中 |
| 消息可见性 | 事务提交后消息才写入分区 | 半消息先写入 CommitLog,提交后标记为可见 |
| 回查机制 | 无回查(Producer 主动提交状态) | Broker 主动回查 Producer 状态 |
| 跨系统事务支持 | 主要用于 Kafka 内部跨分区一致性 | 可与数据库事务绑定(如订单 + 库存) |
| 性能开销 | 较高(涉及 Coordinator 交互和额外主题) | 中等(主要是半消息存储和回查) |
五、典型场景与选择建议
- Kafka 适用场景:
- Kafka Streams 中的 Exactly Once 处理。
- 跨分区 / 主题的原子性写入(如数据聚合、分流)。
- RocketMQ 适用场景:
- 分布式事务(如订单支付成功后发送通知)。
- 本地事务与消息发送强绑定(如库存扣减后发送物流消息)。
- 选择建议:
- 若需与数据库事务强绑定,优先选择 RocketMQ。
- 若在 Kafka 生态内构建流处理,优先选择 Kafka 事务。
六、总结
Kafka 和 RocketMQ 的事务消息均基于两阶段提交思想,但实现细节差异明显:
- Kafka通过 Transaction Coordinator 和幂等性保证跨分区原子性,适合流处理场景。
- RocketMQ通过半消息和状态回查机制,实现本地事务与消息发送的原子性,更适合分布式业务系统。
实际应用中,需根据业务场景选择合适的方案,并注意事务消息带来的性能开销和复杂度。
延迟消息和定时消息
核心概念对比
| 术语 | 定义 | Kafka 支持度 | RocketMQ 支持度 |
|---|---|---|---|
| 定时消息 | 固定时间点触发消费的消息 | 原生不支持 | 支持(基于延迟级别) |
| 延迟消息 | 发送后延迟指定时间触发消费的消息 | 原生不支持 | 支持(18 个预设级别) |
| 实现方式 | 依赖时间轮(TimerWheel)或 Broker 调度 | 需自定义 | 内置实现 |
Kafka 定时 / 延迟消息实现方案
Kafka原生不支持定时 / 延迟消息,需通过以下方式实现:
生产者延迟发送
- 原理:生产者在本地缓存消息,到达指定时间后再发送。
消费者延迟处理
- 原理:消费者拉取消息后,判断时间戳决定是否处理。
第三方组件辅助(如 Kafka Streams)
- 原理:利用 Kafka Streams 的
suppress()操作符延迟输出。
kafka和RocketMQ的一些对比
Kafka 与 RocketMQ 多线程消费对比分析
一、核心设计理念对比
| 维度 | Kafka | RocketMQ |
|---|---|---|
| 原生支持 | 不直接支持,需通过扩展方案实现 | 原生支持多线程并发消费 |
| 线程模型 | 单线程拉取 + 单线程消费(需手动扩展) | 内置线程池处理(默认 20 线程) |
| 顺序性保证 | 分区内严格顺序(依赖单线程消费) | 队列内严格顺序(通过锁机制实现) |
二、多线程实现方案对比
1. Kafka 多线程实现方式
-
消费者组多实例模式
-
原理:多个消费者实例加入同一组,每个实例处理不同分区(分区分配策略如 Range/Round Robin)。
-
优点
:
- 实现简单,无需手动管理线程,利用 Kafka 原生分区分配机制。
- 分区内消息由单线程消费,天然保证顺序性。
-
缺点
:
- 并行度受限于分区数(每个实例至少处理 1 个分区)。
- 多实例间需协调位移提交,可能存在重复消费或丢失风险。
-
-
单实例线程池模式
-
原理:消费者拉取消息后,通过线程池分配给多个线程处理(需手动绑定分区到线程)。
-
优点
:
- 并行度可灵活配置,不受分区数限制(如 1 个分区对应多个线程,但需牺牲顺序性)。
- 适合计算密集型任务,提升 CPU 利用率。
-
缺点
:
- 需手动处理分区 - 线程绑定、位移同步,实现复杂。
- 若未正确绑定分区,可能破坏消息顺序性。
-
2. RocketMQ 多线程实现方式
-
原生线程池消费
-
原理:每个消费者实例内置线程池(默认
ConsumeMessageThreadPoolNums=20),拉取消息后分批次提交到线程池。 -
核心机制
:
- 为每个消息队列(Queue)维护一把锁,消费时需获取锁,确保同一队列同一时间仅一个线程消费(保证顺序性)。
- 消费失败时自动重试(集群模式下发送到重试队列,重试策略可配置)。
-
优点
:
- 开箱即用,无需手动管理线程,配置简单。
- 支持并发消费与顺序消费灵活切换(通过
MessageListenerConcurrently/MessageListenerOrderly接口)。 - 消费进度由 Broker 统一管理,位移提交更可靠。
-
缺点
:
- 线程池参数需根据业务场景调优(如消费耗时高时需增大线程数)。
- 顺序消费时,并行度受限于队列数(一个队列仅一个线程消费)。
-
三、关键能力对比表
| 能力 | Kafka | RocketMQ |
|---|---|---|
| 并行度灵活性 | 消费者组模式受限于分区数; 线程池模式需手动控制 | 原生线程池可动态调整线程数,并行度更高 |
| 顺序性保证 | 分区内严格顺序(多线程需牺牲顺序性) | 队列内严格顺序(通过锁机制自动保证) |
| 位移管理 | 消费者自主提交(需手动处理一致性) | Broker 统一管理消费偏移量,更可靠 |
| 异常处理 | 需自定义重试逻辑(如重试队列) | 内置消费失败重试机制(可配置重试次数、延时) |
| 资源消耗 | 单实例线程数少(依赖操作系统进程调度) | 线程池可能占用更多 JVM 线程资源 |
| 复杂场景支持 | 适合流式处理(结合 Kafka Streams) | 支持事务消息、定时消息等企业级特性 |
四、典型场景适用建议
- Kafka 更适合:
- 大数据日志处理(对顺序性要求低,需高吞吐量)。
- 流式计算场景(结合 Kafka Streams 框架,自动实现多线程并行)。
- 简单场景下,通过消费者组模式快速实现多实例并行。
- RocketMQ 更适合:
- 金融交易、订单系统(需保证队列内消息顺序性,同时支持高并发)。
- 复杂业务场景(如需要消费重试、定时消息、事务消息)。
- 资源管控严格的场景(线程池参数可精准调优)。
五、总结
Kafka 的多线程消费更依赖上层业务逻辑实现,适合追求极简架构或流式处理的场景;RocketMQ 则通过原生线程池和队列锁机制,在保证顺序性的同时提供更便捷的多线程能力,更适合企业级复杂业务。实际选择时,需结合业务对顺序性、并行度、运维复杂度的需求,以及中间件其他特性(如消息重试、事务支持)综合考量。