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