Rocketmq可靠消息

1 消息id可追溯
2

在使用 RocketMQ 发送消息时,消息发送失败是否还能获取到 MessageId,取决于具体的失败场景。以下是详细的场景分析及对应的解决方案:


一、MessageId 生成机制

  1. MessageId 来源

    • RocketMQ 的 MessageId 由 Broker 生成,格式为:BrokerIP+Port+CommitLog Offset
    • 客户端发送时不会生成 MessageId,只有消息成功到达 Broker 并存储后才会生成
  2. 关键时间点

    graph LR A[Producer发送消息] --> B{到达Broker?} B -->|是| C[生成MessageId] B -->|否| D[无MessageId]

二、不同失败场景分析

1. 客户端发送失败(未到达Broker)

  • 场景

    • 网络中断
    • 客户端序列化失败
    • 消息体积超限(超过4MB)
  • 表现

    • 抛出 RemotingExceptionMQClientException 等异常
    • 无法获取 MessageId(因消息未到达Broker)
  • 代码验证

    try {
        SendResult result = rocketMQTemplate.syncSend(topic, message);
        // 此处不会执行
    } catch (RemotingException e) {
        // result 为 null,无法获取 MessageId
    }
    

2. Broker处理失败(消息已到达Broker)

  • 场景

    • Broker 刷盘超时(FLUSH_DISK_TIMEOUT
    • Broker 从节点同步失败(SLAVE_NOT_AVAILABLE
  • 表现

    • 抛出 MQBrokerException
    • 可能获取到 MessageId(消息已存储但未完全持久化)
  • 关键代码

    catch (MQBrokerException e) {
        SendResult partialResult = e.getResponse().getSendResult();
        if (partialResult != null) {
            String msgId = partialResult.getMsgId(); // 可获取到
        }
    }
    

三、生产环境解决方案

方案1:客户端预生成唯一ID(推荐)

// 发送前生成唯一标识
String clientMsgId = "CLIENT_" + UUID.randomUUID();

Message<String> message = MessageBuilder
    .withPayload("payload")
    .setHeader(RocketMQHeaders.KEYS, clientMsgId) // 业务键与客户端ID绑定
    .build();

try {
    SendResult result = rocketMQTemplate.syncSend(topic, message);
    log.info("发送成功, ClientID={}, ServerID={}", clientMsgId, result.getMsgId());
} catch (Exception e) {
    log.error("发送失败, ClientID={}", clientMsgId, e); // 仍能追踪消息
    throw new MessageSendException(clientMsgId, e);
}

方案2:异常处理中提取部分结果

try {
    SendResult result = rocketMQTemplate.syncSend(topic, message);
} catch (MQBrokerException e) {
    // 处理部分成功场景
    if (e.getResponse() != null) {
        SendResult partialResult = e.getResponse().getSendResult();
        if (partialResult != null) {
            auditService.logPartialSuccess(
                partialResult.getMsgId(), 
                e.getErrorMessage()
            );
        }
    }
}

四、事务消息的特殊处理

对于事务消息,需结合本地事务状态判断是否生成 MessageId:

@RocketMQTransactionListener
public class TxListenerImpl implements RocketMQLocalTransactionListener {

    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        try {
            // 执行本地事务
            boolean success = dbService.doBusiness();
            return success ? COMMIT : ROLLBACK;
        } catch (Exception e) {
            return UNKNOWN; // 触发回查
        }
    }

    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
        // 检查本地事务状态
        return dbService.isCompleted(msg.getKeys()) ? COMMIT : ROLLBACK;
    }
}

事务消息的 MessageId 生成规则

  • 事务提交后才会生成有效 MessageId
  • 若事务回滚或超时,Broker 会删除消息,此时无 MessageId

五、监控与排查建议

  1. 日志规范

    // 在消息发送入口统一记录
    MDC.put("msgKey", clientMsgId);
    logger.info("开始发送消息");
    try {
        // 发送逻辑
    } finally {
        MDC.clear();
    }
    
  2. 监控指标

    # Prometheus 指标示例
    rocketmq_send_total{topic="$topic",status="success"} 238
    rocketmq_send_total{topic="$topic",status="failure"} 12
    rocketmq_send_latency_seconds_bucket{topic="$topic",le="0.1"} 89
    
  3. 消息轨迹查询

    # 使用官方工具查询
    mqadmin queryMsgById -n 127.0.0.1:9876 -i "0A9F00E064C818B4AAC25B1B7D200000"
    

六、各场景总结表

失败场景 能否获取 MessageId 解决方案
客户端未发出 客户端生成唯一ID
网络超时 重试机制 + 客户端ID
Broker存储失败(部分成功) 解析异常中的 SendResult
事务消息回滚 依赖本地事务日志
ACL鉴权失败 客户端ID + 错误日志分析

通过以上方案,可确保 100% 消息可追踪,即使发送失败也能通过客户端生成的唯一 ID 进行后续处理(如人工补偿、自动重试等)。建议将客户端 ID 与业务数据共同持久化,实现端到端的可靠性保障。

posted @ 2025-03-09 08:10  向着朝阳  阅读(95)  评论(0)    收藏  举报