目录
1:写在前面
2:RocketMQ消息数据结构
3:生产者启动流程
3.1:DefaultMQProducer
3.2:启动生产者具体的实现类 DefaultMQProducerImpl
3.3:同步消息发送基本流程
4:总结
目录
RockerMQ发送一般的消息(还有一种是事务消息,后续的博客会展开)有三种实现方式:可靠的同步传输,可靠的异步传输,单向传输,这些传输方式的前提都是基于消息体(消息数据结构),发送消息必然要有一个客户端(涉及到客户端的启动流程,是怎么初始化的),还有消息发送的流程,从哪里开始发送,发送到哪里,这都是我们需要考虑到的问题,上述只是基于单条消息的发送,要是涉及到多条消息的发送,是循环调用消息发送接口呢,还是在内部做了什么优化?以下将会具体分析。
org.apache.rocketmq.common.message.Message
//主题
private String topic;
//目前不做处理的属性
private int flag;
//扩展属性, tag , delayTimeLevel 消息延迟级别,用于定时消息或消息重试
private Map properties;
//消息体的字节数组
private byte[] body;
//事务,在后续的事务消息中有使用到,主要用于事务消息的状态会查
private String transactionId;
作为MQ的默认的消息发送者客户端,实现了MQAdmin接口,提供了以下重要的接口
//创建topic
org.apache.rocketmq.client.producer.DefaultMQProducer#createTopic(java.lang.String, java.lang.String, int, int)
//消息发送with超时 --- 到broker的返回时间
org.apache.rocketmq.client.producer.DefaultMQProducer#send(org.apache.rocketmq.common.message.Message, long)
//带回调+超时
org.apache.rocketmq.client.producer.DefaultMQProducer#send(org.apache.rocketmq.common.message.Message, org.apache.rocketmq.client.producer.SendCallback, long)
//单向的
org.apache.rocketmq.client.producer.DefaultMQProducer#sendOneway(org.apache.rocketmq.common.message.Message)
//指定消息队列MessageQueueSelector ---- 顺序消息的实现
org.apache.rocketmq.client.producer.DefaultMQProducer#sendOneway(org.apache.rocketmq.common.message.Message, org.apache.rocketmq.client.producer.MessageQueueSelector, java.lang.Object)
//事务消息
org.apache.rocketmq.client.producer.DefaultMQProducer#sendMessageInTransaction(org.apache.rocketmq.common.message.Message, org.apache.rocketmq.client.producer.LocalTransactionExecuter, java.lang.Object)
//批量发送消息
org.apache.rocketmq.client.producer.DefaultMQProducer#send(java.util.Collection, org.apache.rocketmq.common.message.MessageQueue, long)
一些核心的属性
//生产者所属组,主要用于事务回查
private String producerGroup;
//默认的topicKey
private String createTopicKey = MixAll.AUTO_CREATE_TOPIC_KEY_TOPIC;
//默认的主题队列大小
private volatile int defaultTopicQueueNums = 4;
//发送消息默认的超时时间 3s
private int sendMsgTimeout = 3000;
//当消息体的大小达到多少时启动压缩机制 默认4k
private int compressMsgBodyOverHowmuch = 1024 * 4;
//消息发送失败的时候需要重试的次数,默认2 加上一开始的一次总共三次
private int retryTimesWhenSendFailed = 2;
//异步消息发送失败重试次数,和以上保持一致
private int retryTimesWhenSendAsyncFailed = 2;
//消息发送失败是否选择另外的broker,不存储就返回。
private boolean retryAnotherBrokerWhenNotStoreOK = false;
//允许发送消息的最大长度,默认4M,基本山不用动,要是一个消息体有4M,系统就需要优化了
private int maxMessageSize = 1024 * 1024 * 4; // 4M
DefaultMQProducerImpl 是DefaultMQProducer里面的一个组合对象
按照以往的风格,一开始是调用start方法
public void start(final boolean startFactory) throws MQClientException {
switch (this.serviceState) {
//获取生产者的状态
case CREATE_JUST:
this.serviceState = ServiceState.START_FAILED;
//检查配置项
this.checkConfig();
//如果不是默认的生产者组,就要把当前实例的名称换成为当前进程的id
if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {
this.defaultMQProducer.changeInstanceNameToPID();
}
//创建MQClientInstance 用于消息的发送
this.mQClientFactory = MQClientManager.getInstance().getAndCreateMQClientInstance(this.defaultMQProducer, rpcHook);
//向客户端工厂注册自己生产者,便于后续调用网络请求,或者心跳检测等等,如果注册失败的话,将会抛出异常
boolean registerOK = mQClientFactory.registerProducer(this.defaultMQProducer.getProducerGroup(), this);
if (!registerOK) {
this.serviceState = ServiceState.CREATE_JUST;
throw new MQClientException("The producer group[" + this.defaultMQProducer.getProducerGroup()
+ "] has been created before, specify another name please." + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL),
null);
}
//注册topic注册表,便于找到对应的主题
this.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo());
if (startFactory) {
mQClientFactory.start();
}
log.info("the producer [{}] start OK. sendMessageWithVIPChannel={}", this.defaultMQProducer.getProducerGroup(),
this.defaultMQProducer.isSendMessageWithVIPChannel());
this.serviceState = ServiceState.RUNNING;
break;
case RUNNING:
case START_FAILED:
case SHUTDOWN_ALREADY:
throw new MQClientException("The producer service state not OK, maybe started once, "
+ this.serviceState
+ FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK),
null);
default:
break;
}
this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();
}
1:org.apache.rocketmq.client.producer.DefaultMQProducer#send(org.apache.rocketmq.common.message.Message)
2:org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#send(org.apache.rocketmq.common.message.Message)
3:org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendDefaultImpl
public SendResult send(Message msg,
long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
//设置消息模式为同步模式,超时时间使用默认的 3s
return this.sendDefaultImpl(msg, CommunicationMode.SYNC, null, timeout);
}
4:校验消息长度
public static void checkMessage(Message msg, DefaultMQProducer defaultMQProducer)
throws MQClientException {
if (null == msg) {
throw new MQClientException(ResponseCode.MESSAGE_ILLEGAL, "the message is null");
}
// topic
Validators.checkTopic(msg.getTopic());
// body
if (null == msg.getBody()) {
throw new MQClientException(ResponseCode.MESSAGE_ILLEGAL, "the message body is null");
}
if (0 == msg.getBody().length) {
throw new MQClientException(ResponseCode.MESSAGE_ILLEGAL, "the message body length is zero");
}
if (msg.getBody().length > defaultMQProducer.getMaxMessageSize()) {
throw new MQClientException(ResponseCode.MESSAGE_ILLEGAL,
"the message body size over max value, MAX: " +
// 不能超过4M
defaultMQProducer.getMaxMessageSize());
}
}
5:在发送消息的时候,消息是发送到broker上面的,在此之前,必须要找到对应的主题,俗称主题路由
//查找顺序
// topic -----》 topicPublishInfoTable(缓存) ----- 》 InfoNameServer
// 返回的是一个 根据主题查找的 消息队列 TopicPublishInfo 包含消息队列,路由信息,broker
private TopicPublishInfo tryToFindTopicPublishInfo(final String topic) {
//根据主题在缓存中查找,如果没有找到,那么主题注册表就创建一个新的,然后更新注册表
TopicPublishInfo topicPublishInfo = this.topicPublishInfoTable.get(topic);
if (null == topicPublishInfo || !topicPublishInfo.ok()) {
this.topicPublishInfoTable.putIfAbsent(topic, new TopicPublishInfo());
//然后更新注册表
this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic);
//重新获取,原因是因为其他的生产者可能也是同样的主题,这样子就可以获取最新的主题注册表了
topicPublishInfo = this.topicPublishInfoTable.get(topic);
}
//如果注册表中当前主题的对应的路由信息,那么返回
if (topicPublishInfo.isHaveTopicRouterInfo() || topicPublishInfo.ok()) {
return topicPublishInfo;
} else {
//更新nameServer中注册表
this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic, true, this.defaultMQProducer);
//获取最新的
topicPublishInfo = this.topicPublishInfoTable.get(topic);
return topicPublishInfo;
}
}
private boolean orderTopic = false;
private boolean haveTopicRouterInfo = false;
//返回的消息队列
private List messageQueueList = new ArrayList();
private volatile ThreadLocalIndex sendWhichQueue = new ThreadLocalIndex();
//路由信息
private TopicRouteData topicRouteData;
3.4:选择消息队列
在3.3中已经获取到了消息发送者需要的消息队列的相关信息,因为在MQ中默认会有四个消息队列,那么RocketMQ是怎么选择某一个消息队列的呢?
public MessageQueue selectOneMessageQueue(final String lastBrokerName) {
//缓存机制,先查找上一次使用发送失败的broker,如果失败,说明需要重新选择一个broker
if (lastBrokerName == null) {
return selectOneMessageQueue();
} else {
//取模获得消息队列的下标
int index = this.sendWhichQueue.getAndIncrement();
for (int i = 0; i < this.messageQueueList.size(); i++) {
int pos = Math.abs(index++) % this.messageQueueList.size();
if (pos < 0)
pos = 0;
//从消息列表list中拿一个队列出来进行消息信道的存放
MessageQueue mq = this.messageQueueList.get(pos);
if (!mq.getBrokerName().equals(lastBrokerName)) {
return mq;
}
}
return selectOneMessageQueue();
}
}
为什么上次出故障的broker还会出现在列表中呢?这是因为 连接nameserver的时候,会存在这扫描的延迟,也就是说,在broker宕机的时间内,nameserver还没有感知到,nameserver的心跳检测频率为 10s/次
3.5:使用消息队列发送消息
在3.4中已经获取到了即将要放松的消息队列(和broker绑定的),接下来一起看看是怎么发送消息的。
org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendKernelImpl
//用于判定消息的发送是否超时
long beginStartTime = System.currentTimeMillis();
//根据brokername获取到broker地址
String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());
if (null == brokerAddr) {
//如果没有找到的话,就需要重新根据topic查找路由信息,里面涉及到更新
tryToFindTopicPublishInfo(mq.getTopic());
brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());
}
SendMessageContext context = null;
if (brokerAddr != null) {
brokerAddr = MixAll.brokerVIPChannel(this.defaultMQProducer.isSendMessageWithVIPChannel(), brokerAddr);
//获取到消息的消息体 字节数组
byte[] prevBody = msg.getBody();
try {
//for MessageBatch,ID has been set in the generating process
//看下是否是批量消息 MessageBatch extends Message
if (!(msg instanceof MessageBatch)) {
MessageClientIDSetter.setUniqID(msg);
}
boolean topicWithNamespace = false;
if (null != this.mQClientFactory.getClientConfig().getNamespace()) {
msg.setInstanceId(this.mQClientFactory.getClientConfig().getNamespace());
topicWithNamespace = true;
}
int sysFlag = 0;
boolean msgBodyCompressed = false;
//看下消息是否要压缩,4k
if (this.tryToCompressMessage(msg)) {
sysFlag |= MessageSysFlag.COMPRESSED_FLAG;
msgBodyCompressed = true;
}
//是否设置了事务消息
final String tranMsg = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
if (tranMsg != null && Boolean.parseBoolean(tranMsg)) {
//如果是事务消息的话,会给消息一个 PRE 的标志,后续会添加一个事务消息的专用处理器
sysFlag |= MessageSysFlag.TRANSACTION_PREPARED_TYPE;
}
接下来要做的就是封装消息体,形成约定的消息格式进行传输,创建请求头
//创建消息请求头
SendMessageRequestHeader requestHeader = new SendMessageRequestHeader();
//设置生产者组
requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());
//设置消息主题
requestHeader.setTopic(msg.getTopic());
requestHeader.setDefaultTopic(this.defaultMQProducer.getCreateTopicKey());
//默认的消息队列的数量 默认4个
requestHeader.setDefaultTopicQueueNums(this.defaultMQProducer.getDefaultTopicQueueNums());
//设置消息队列id
requestHeader.setQueueId(mq.getQueueId());
//消息的类型
requestHeader.setSysFlag(sysFlag);
requestHeader.setBornTimestamp(System.currentTimeMillis());
requestHeader.setFlag(msg.getFlag());
//设置消息属性,里面包括了 tag等信息
requestHeader.setProperties(MessageDecoder.messageProperties2String(msg.getProperties()));
requestHeader.setReconsumeTimes(0);
requestHeader.setUnitMode(this.isUnitMode());
//设置是否为批量消息
requestHeader.setBatch(msg instanceof MessageBatch);
开始发送消息
//发送结果
SendResult sendResult = null;
switch (communicationMode) {
//同步模式下发送消息
case ASYNC:
Message tmpMessage = msg;
boolean messageCloned = false;
//消息是否被压缩过 4k
if (msgBodyCompressed) {
//If msg body was compressed, msgbody should be reset using prevBody.
//Clone new message using commpressed message body and recover origin massage.
//Fix bug:https://github.com/apache/rocketmq-externals/issues/66
tmpMessage = MessageAccessor.cloneMessage(msg);
messageCloned = true;
msg.setBody(prevBody);
}
if (topicWithNamespace) {
if (!messageCloned) {
tmpMessage = MessageAccessor.cloneMessage(msg);
messageCloned = true;
}
msg.setTopic(NamespaceUtil.withoutNamespace(msg.getTopic(), this.defaultMQProducer.getNamespace()));
}
//一共花费了多长时间
long costTimeAsync = System.currentTimeMillis() - beginStartTime;
//超出了默认的超时 时间,就会抛出异常
if (timeout < costTimeAsync) {
throw new RemotingTooMuchRequestException("sendKernelImpl call timeout");
}
//开始发送消息
sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage(
brokerAddr,
mq.getBrokerName(),
tmpMessage,
requestHeader,
timeout - costTimeAsync,
communicationMode,
sendCallback,
topicPublishInfo,
this.mQClientFactory,
this.defaultMQProducer.getRetryTimesWhenSendAsyncFailed(),
context,
this);
break;
发送消息的核心方法解析
org.apache.rocketmq.client.impl.MQClientAPIImpl#sendMessage(java.lang.String, java.lang.String, org.apache.rocketmq.common.message.Message, org.apache.rocketmq.common.protocol.header.SendMessageRequestHeader, long, org.apache.rocketmq.client.impl.CommunicationMode, org.apache.rocketmq.client.producer.SendCallback, org.apache.rocketmq.client.impl.producer.TopicPublishInfo, org.apache.rocketmq.client.impl.factory.MQClientInstance, int, org.apache.rocketmq.client.hook.SendMessageContext, org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl)
//开始发送消息
sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage(
brokerAddr,
mq.getBrokerName(),
tmpMessage,
requestHeader,
timeout - costTimeAsync,
communicationMode,
sendCallback,
topicPublishInfo,
this.mQClientFactory,
this.defaultMQProducer.getRetryTimesWhenSendAsyncFailed(),
context,
this);
由于本节讨论的均为同步模式,则以同步模式分析
//用于消息发送超时判断
long beginStartTime = System.currentTimeMillis();
RemotingCommand request = null;
if (sendSmartMsg || msg instanceof MessageBatch) {
SendMessageRequestHeaderV2 requestHeaderV2 = SendMessageRequestHeaderV2.createSendMessageRequestHeaderV2(requestHeader);
request = RemotingCommand.createRequestCommand(msg instanceof MessageBatch ? RequestCode.SEND_BATCH_MESSAGE : RequestCode.SEND_MESSAGE_V2, requestHeaderV2);
} else {
request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, requestHeader);
}
//请求里面设置请求的消息的字节数组
request.setBody(msg.getBody());
switch (communicationMode) {
case ONEWAY:
this.remotingClient.invokeOneway(addr, request, timeoutMillis);
return null;
case ASYNC:
final AtomicInteger times = new AtomicInteger();
//消息超时校验
long costTimeAsync = System.currentTimeMillis() - beginStartTime;
if (timeoutMillis < costTimeAsync) {
throw new RemotingTooMuchRequestException("sendMessage call timeout");
}
//发送同步消息
this.sendMessageAsync(addr, brokerName, msg, timeoutMillis - costTimeAsync, request, sendCallback, topicPublishInfo, instance,
retryTimesWhenSendFailed, times, context, producer);
return null;
同步发送消息开始 使用 org.apache.rocketmq.client.impl.MQClientAPIImpl#sendMessageAsync
RemotingCommand response = responseFuture.getResponseCommand();
if (null == sendCallback && response != null) {
try {
//使用MQClientAPI来发送消息,在MQ中都是,客户端和服务器之间的通信都是通过 MQClientAPIImpl 来的
SendResult sendResult = MQClientAPIImpl.this.processSendResponse(brokerName, msg, response);
if (context != null && sendResult != null) {
context.setSendResult(sendResult);
context.getProducer().executeSendMessageHookAfter(context);
}
} catch (Throwable e) {
}
producer.updateFaultItem(brokerName, System.currentTimeMillis() - responseFuture.getBeginTimestamp(), false);
return;
}
org.apache.rocketmq.client.impl.MQClientAPIImpl#processSendResponse
private SendResult processSendResponse(
final String brokerName,
final Message msg,
final RemotingCommand response
) throws MQBrokerException, RemotingCommandException {
switch (response.getCode()) {
case ResponseCode.FLUSH_DISK_TIMEOUT:
case ResponseCode.FLUSH_SLAVE_TIMEOUT:
case ResponseCode.SLAVE_NOT_AVAILABLE: {
}
case ResponseCode.SUCCESS: {
SendStatus sendStatus = SendStatus.SEND_OK;
switch (response.getCode()) {
case ResponseCode.FLUSH_DISK_TIMEOUT:
sendStatus = SendStatus.FLUSH_DISK_TIMEOUT;
break;
case ResponseCode.FLUSH_SLAVE_TIMEOUT:
sendStatus = SendStatus.FLUSH_SLAVE_TIMEOUT;
break;
case ResponseCode.SLAVE_NOT_AVAILABLE:
sendStatus = SendStatus.SLAVE_NOT_AVAILABLE;
break;
case ResponseCode.SUCCESS:
sendStatus = SendStatus.SEND_OK;
break;
default:
assert false;
break;
}
SendMessageResponseHeader responseHeader =
(SendMessageResponseHeader) response.decodeCommandCustomHeader(SendMessageResponseHeader.class);
//If namespace not null , reset Topic without namespace.
String topic = msg.getTopic();
if (StringUtils.isNotEmpty(this.clientConfig.getNamespace())) {
topic = NamespaceUtil.withoutNamespace(topic, this.clientConfig.getNamespace());
}
//组装有一个消息队列,(主题,broker服务器名称,消息队列序号)
MessageQueue messageQueue = new MessageQueue(topic, brokerName, responseHeader.getQueueId());
String uniqMsgId = MessageClientIDSetter.getUniqID(msg);
//批量消息的处理
if (msg instanceof MessageBatch) {
StringBuilder sb = new StringBuilder();
for (Message message : (MessageBatch) msg) {
sb.append(sb.length() == 0 ? "" : ",").append(MessageClientIDSetter.getUniqID(message));
}
uniqMsgId = sb.toString();
}
SendResult sendResult = new SendResult(sendStatus,
uniqMsgId,
responseHeader.getMsgId(), messageQueue, responseHeader.getQueueOffset());
//返回结果,设置一个事务id
sendResult.setTransactionId(responseHeader.getTransactionId());
String regionId = response.getExtFields().get(MessageConst.PROPERTY_MSG_REGION);
String traceOn = response.getExtFields().get(MessageConst.PROPERTY_TRACE_SWITCH);
if (regionId == null || regionId.isEmpty()) {
regionId = MixAll.DEFAULT_TRACE_REGION_ID;
}
if (traceOn != null && traceOn.equals("false")) {
sendResult.setTraceOn(false);
} else {
sendResult.setTraceOn(true);
}
sendResult.setRegionId(regionId);
return sendResult;
}
default:
break;
}
throw new MQBrokerException(response.getCode(), response.getRemark());
}
脉络流程图