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等屬性存入到重試消息屬性中。

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