RocketMQ生成消息ID逻辑

1. 背景

消息发送之前,会在客户端创建一个msgId,发送到broker后,broker在保存消息到本地时也会创建一个msgId。

2. 客户端生成消息ID时机与机制

2.1 生成消息ID时机

当生产者发送消息的基本流程如下:

  • Step 1:根据MessageQueue获取Broker的网络地址
  • Step 2:为消息分配全局唯一ID(默认4K,超过则压缩且设置消息系统标记COMPRESSED_FLAG,事务型消息TRANSACTION_PREPARED_TYPE)
  • Step 3:如果注册了消息发送钩子函数,则执行消息发送之前的增强逻辑
  • Step 4:构造消息发送请求包(如生产者组、topic、队列ID等 )
  • Step 5:根据发送方式,同步、异步和单向方式进行网络传输(发送给Broker的netty code:SEND_MESSAGE)
  • Step 6:如果注册了消息发送钩子函数,则执行after逻辑

所以客户端即生产者第一次生成消息ID时机在发送消息前会在客户端生成消息ID

2.2 生成消息ID机制

通过DefaultMQProducerImpl#sendKernelImpl方法定位到MessageClientIDSetter#createUniqID方法中,即

public class MessageClientIDSetter {
    private static final String TOPIC_KEY_SPLITTER = "#";
    private static final int LEN;
    private static final String FIX_STRING;
    private static final AtomicInteger COUNTER;
    private static long startTime;
    private static long nextStartTime;

    static {
        LEN = 4 + 2 + 4 + 4 + 2;
        ByteBuffer tempBuffer = ByteBuffer.allocate(10);
        tempBuffer.position(2);
        tempBuffer.putInt(UtilAll.getPid());
        tempBuffer.position(0);
        try {
            tempBuffer.put(UtilAll.getIP()); // IP
        } catch (Exception e) {
            tempBuffer.put(createFakeIP());
        }
        tempBuffer.position(6);
        tempBuffer.putInt(MessageClientIDSetter.class.getClassLoader().hashCode()); // classloader的hashCode
        FIX_STRING = UtilAll.bytes2string(tempBuffer.array());
        setStartTime(System.currentTimeMillis());
        COUNTER = new AtomicInteger(0);
    }
  
    
    public static String createUniqID() {
            StringBuilder sb = new StringBuilder(LEN * 2);
            sb.append(FIX_STRING);
            sb.append(UtilAll.bytes2string(createUniqIDBuffer()));
            return sb.toString();
    }
}

3. Broker生成消息ID时机与机制

3.1 生成消息ID时机

生产者发送消息的基本流程的第5步时,生产者发送消息时会带上发送给Broker的netty code:SEND_MESSAGE,Broker收到该消息时会使用SendMessageProcessor的sendMessage方法来处理发送消息流程,即发送消息。源码如下:

private RemotingCommand sendMessage(final ChannelHandlerContext ctx,
                                        final RemotingCommand request,
                                        final SendMessageContext sendMessageContext,
                                        final SendMessageRequestHeader requestHeader) throws RemotingCommandException {
  // .......
  if (traFlag != null && Boolean.parseBoolean(traFlag)) {
    //.....
    putMessageResult = this.brokerController.getTransactionalMessageService().prepareMessage(msgInner);
  } else {
    // 存储消息到Broker本地
    putMessageResult = this.brokerController.getMessageStore().putMessage(msgInner);
  }
  return handlePutMessageResult(putMessageResult, response, request, msgInner, responseHeader, sendMessageContext, ctx, queueIdInt);
}

所以Broker生成消息ID时机在Broker存储消息到本地时。

3.2 生成消息ID机制

SendMessageProcessor的sendMessage方法进入到DefaultMessageStore的putMessage方法中,最终进入到MessageDecoder.createMessageId的方法中,如下:

public static String createMessageId(final ByteBuffer input, final ByteBuffer addr, final long offset) {
        input.flip();
        input.limit(MessageDecoder.MSG_ID_LENGTH);

        input.put(addr);  // 机器IP + 端口 共16位
        input.putLong(offset); // Commitlog的offset  共16位

        return UtilAll.bytes2string(input.array());
}

4. 生产者端发送消息产生的ID与Broker存放消息时生成消息ID区别

很奇怪为什么有两个消息ID,咨询了rocketmq的作者丁威后,以下是问题和原话:

  • 问题:请问生产者在发送消息时,在发送端发送消息给broker时会产生一个消息ID,另外Broker存放消息时生成消息ID,这两个消息ID的区别和联系是什么,我们常说的消息ID指的是哪个?
  • 答案:发送端生成的id称为 msgId ,服务端生成的那个id 称为 offsetMsgId,记录了在服务端的物理偏移量等信息,发送方发送一条消息,会返回 msgId msgOffsetId ,如果在该条消息在消费时失败重试时,会再次发送到broker,产生一条新的消息,这条消息与“同源”的消息的msgid相同,但offsetmsgid不同,然后接下来的时候,建议自己在消费消息的时候,看看返回的是哪个msgId,实战一下,效果更好。

客户端(生产者)在发送消息时,若是同步发送消息生产者只会向Broker发送一次,参考DefaultMQProducerImpl的sendDefaultImpl方法。消息服务端(Broker端)会将客户端(生产者)发送的消息存储在服务端,日志文件会生成一个 msgId,即msgOffsetId。上面所说的消息消费失败重试时 ,消息消费端会调用到MQClientAPIImpl#consumerSendMessageBack方法向Broker服务端发送CONSUMER_SEND_MSG_BACK指令,消息服务端(Broker端)收到此指令会根据原先的消息创建一个新的消息对象,该重试消息会拥有自己的唯一消息ID并存入到commitlog文件中,该重试消息并不会去更新原先的消息,而是将原先消息的主题、原消息ID等属性存入到重试消息属性中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章