MQ常见问题

78 阅读18分钟

消息积压

产生消息积压的原因

常见造成消息积压的原因:

  • 生产者流量徒增
  • 消费者机器故障导致消息消费能力下降
  • 单条消息处理耗时过长
  • 消费线程配置不合理

解决方式

生产端限流

不建议

增加消费端能力

增加消费端能力的方式:

  • 能加消费者数量(在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.messageslog.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 + 事务状态回查),确保本地事务与消息发送的一致性:
    1. 发送 Half Message 到 Broker(不可见)。
    2. 执行本地事务。
    3. 根据事务结果提交或回滚 Half Message(提交后消息可见)。 若 Producer 宕机,Broker 通过回查机制确认事务状态,避免消息丢失。
  • 定时重试与批量发送
    • 发送失败时自动重试(可配置重试次数和间隔)。
    • 批量发送时若部分消息失败,支持重试或跳过,避免整个批次丢失。
2. 存储端:刷盘与主从复制
  • 刷盘机制
    • 异步刷盘(默认):消息写入内存映射文件(PageCache)后即响应,由后台线程定期刷盘,性能高但可能丢失少量未刷盘的消息。
    • 同步刷盘:消息写入 PageCache 并刷盘到磁盘后才响应,可靠性高但性能略低。
  • 主从复制与 HA 机制
    • RocketMQ 通过主从架构(Master-Slave)实现数据冗余,Master 负责读写,Slave 从 Master 同步数据。
    • HA(High Availability)机制:Slave 主动从 Master 拉取数据,或 Master 推送给 Slave,确保数据同步。
    • 可配置 brokerRoleSYNC_MASTER(同步模式,Slave 同步后才响应)或 ASYNC_MASTER(异步模式),平衡性能与可靠性。
  • CommitLog 与 ConsumeQueue
    • 消息存储在 CommitLog(全局有序),ConsumeQueue(分区逻辑队列)记录消息偏移量,即使 ConsumeQueue 损坏也可通过 CommitLog 重建,避免消费偏移量丢失。
3. 消费端:精确消费与重试机制
  • 消费模式与偏移量管理
    • 集群消费:多个消费者实例分摊消费,偏移量由 Broker 维护(存在 ConsumeQueue 中),避免客户端本地存储导致的偏移量丢失。
    • 广播消费:每个消费者消费全量消息,偏移量存储在客户端,需确保本地持久化。
  • 重试队列与死信队列
    • 消费失败时,消息自动进入重试队列(默认 16 次重试,每次间隔递增),重试失败后进入死信队列(DLQ,每个 Topic 对应一个 % DLQ% 后缀的死信 Topic),避免消息被直接丢弃。
    • 可通过配置 maxReconsumeTimes 控制重试次数,死信队列中的消息可手动处理。
  • 拉取与推送模式
    • 拉取模式(Pull):消费者主动拉取消息,处理完后才更新偏移量,避免未处理的消息被标记为已消费。
    • 推送模式(Push,基于长轮询实现):Broker 主动推送消息,但消费者可通过流控机制(如暂停拉取)控制消费节奏,防止过载导致消息处理失败。

三、两者对比与核心差异

维度KafkaRocketMQ
生产端事务基于 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);
    

顺序消费的局限性

  1. 吞吐量瓶颈:顺序消费必然导致单线程处理,无法充分利用多核 CPU。
  2. 故障恢复延迟:若消费线程崩溃,需等待重启后才能继续处理后续消息。
  3. 适用场景有限:仅适用于强依赖消息顺序的业务(如订单状态流转),多数场景可通过业务层幂等性降低对顺序的依赖。

事务消息

Kafka 与 RocketMQ 事务消息原理对比

一、核心设计目标
维度KafkaRocketMQ
事务定义跨分区 / 主题的原子性写入本地事务与消息发送的原子性绑定
适用场景流处理中的 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);
四、核心差异对比表
维度KafkaRocketMQ
事务协调者依赖 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 多线程消费对比分析

一、核心设计理念对比
维度KafkaRocketMQ
原生支持不直接支持,需通过扩展方案实现原生支持多线程并发消费
线程模型单线程拉取 + 单线程消费(需手动扩展)内置线程池处理(默认 20 线程)
顺序性保证分区内严格顺序(依赖单线程消费)队列内严格顺序(通过锁机制实现)
二、多线程实现方案对比
1. Kafka 多线程实现方式
  • 消费者组多实例模式

    • 原理:多个消费者实例加入同一组,每个实例处理不同分区(分区分配策略如 Range/Round Robin)。

    • 优点

      • 实现简单,无需手动管理线程,利用 Kafka 原生分区分配机制。
      • 分区内消息由单线程消费,天然保证顺序性。
    • 缺点

      • 并行度受限于分区数(每个实例至少处理 1 个分区)。
      • 多实例间需协调位移提交,可能存在重复消费或丢失风险。
  • 单实例线程池模式

    • 原理:消费者拉取消息后,通过线程池分配给多个线程处理(需手动绑定分区到线程)。

    • 优点

      • 并行度可灵活配置,不受分区数限制(如 1 个分区对应多个线程,但需牺牲顺序性)。
      • 适合计算密集型任务,提升 CPU 利用率。
    • 缺点

      • 需手动处理分区 - 线程绑定、位移同步,实现复杂。
      • 若未正确绑定分区,可能破坏消息顺序性。
2. RocketMQ 多线程实现方式
  • 原生线程池消费

    • 原理:每个消费者实例内置线程池(默认ConsumeMessageThreadPoolNums=20),拉取消息后分批次提交到线程池。

    • 核心机制

      • 为每个消息队列(Queue)维护一把锁,消费时需获取锁,确保同一队列同一时间仅一个线程消费(保证顺序性)。
      • 消费失败时自动重试(集群模式下发送到重试队列,重试策略可配置)。
    • 优点

      • 开箱即用,无需手动管理线程,配置简单。
      • 支持并发消费与顺序消费灵活切换(通过MessageListenerConcurrently/MessageListenerOrderly接口)。
      • 消费进度由 Broker 统一管理,位移提交更可靠。
    • 缺点

      • 线程池参数需根据业务场景调优(如消费耗时高时需增大线程数)。
      • 顺序消费时,并行度受限于队列数(一个队列仅一个线程消费)。
三、关键能力对比表
能力KafkaRocketMQ
并行度灵活性消费者组模式受限于分区数; 线程池模式需手动控制原生线程池可动态调整线程数,并行度更高
顺序性保证分区内严格顺序(多线程需牺牲顺序性)队列内严格顺序(通过锁机制自动保证)
位移管理消费者自主提交(需手动处理一致性)Broker 统一管理消费偏移量,更可靠
异常处理需自定义重试逻辑(如重试队列)内置消费失败重试机制(可配置重试次数、延时)
资源消耗单实例线程数少(依赖操作系统进程调度)线程池可能占用更多 JVM 线程资源
复杂场景支持适合流式处理(结合 Kafka Streams)支持事务消息、定时消息等企业级特性
四、典型场景适用建议
  • Kafka 更适合
    • 大数据日志处理(对顺序性要求低,需高吞吐量)。
    • 流式计算场景(结合 Kafka Streams 框架,自动实现多线程并行)。
    • 简单场景下,通过消费者组模式快速实现多实例并行。
  • RocketMQ 更适合
    • 金融交易、订单系统(需保证队列内消息顺序性,同时支持高并发)。
    • 复杂业务场景(如需要消费重试、定时消息、事务消息)。
    • 资源管控严格的场景(线程池参数可精准调优)。
五、总结

Kafka 的多线程消费更依赖上层业务逻辑实现,适合追求极简架构或流式处理的场景;RocketMQ 则通过原生线程池和队列锁机制,在保证顺序性的同时提供更便捷的多线程能力,更适合企业级复杂业务。实际选择时,需结合业务对顺序性、并行度、运维复杂度的需求,以及中间件其他特性(如消息重试、事务支持)综合考量。