文章目录
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等属性存入到重试消息属性中。