RocketMQ发送普通消息有三种方式:可靠同步发送、可靠异步发送、单向发送【OneWay】。[RocketMQ消息结构-消息生产者启动流程-消息发送流程-批量消息发送]
一、RocketMQ消息发送
支持三种发送方式:同步、异步、单向。

RocketMQ消息发送需要考虑的几个问题:消息队列如何进行负载、消息发送如何实现高可用、批量消息发送如何实现一致性。
二、RocketMQ消息-Message
Message消息类:
package org.apache.rocketmq.common.message;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class Message implements Serializable {
private static final long serialVersionUID = 8445773977080406428L;
private String topic;
private int flag;
private Map<String, String> properties;
private byte[] body;
private String transactionId;
public Message() {
}
public Message(String topic, byte[] body) {
this(topic, "", "", 0, body, true);
}
public Message(String topic, String tags, String keys, int flag, byte[] body, boolean waitStoreMsgOK) {
}
public Message(String topic, String tags, byte[] body) {
this(topic, tags, "", 0, body, true);
}
public Message(String topic, String tags, String keys, byte[] body) {
this(topic, tags, keys, 0, body, true);
}
void clearProperty(final String name) {
if (null != this.properties) {
this.properties.remove(name);
}
}
public void putUserProperty(final String name, final String value) {
}
public boolean isWaitStoreMsgOK() {
}
}
Message消息属性主要包括所属主题topic、消息flag、扩展属性、消息体;
Message扩展属性主要包含以下几个:
tag:消息TAG,用于消息过滤;
keys:Message索引键,多个用空格隔开,RocketMQ可以根据这些key快速检索到消息 ;
waitStoreMsgOK:消息发送时是否等消息存储完成后再返回;
delayTimeLevel:消息延迟级别,用于定时消息或者消息重试;这些扩展属性在Message的properties中。
三、生产者启动流程
消息生产模块的代码在Client中,对于RocketMQ来讲,它是消息的提供者,也就是客户端。
1、DefaultMQProducer【消息发送者】
DefaultMQProducer是默认的消息生产者实现类,实现MQAdmin接口,
该方法用于创建主题。
//key:目前未实际应用,可以与newTopic相同
//newTopic: 主题名称
//queueNum : 队列数量
//topicSysFlag : 主题系统标签
void createTopic(String key, String newTopic, int queueNum, int topicSysFlag)
throws MQClientException;
根据时间戳从队列中查找其偏移量。
long searchOffset(final MessageQueue mq, final long timestamp) throws MQClientException;



DefaultMQProducer核心属性:
package org.apache.rocketmq.client.producer;
public class DefaultMQProducer extends ClientConfig implements MQProducer {
private final InternalLogger log = ClientLogger.getLog();
protected final transient DefaultMQProducerImpl defaultMQProducerImpl;
//生产者所属组,消息服务器在回查事务状态时会随机选择该组中任何一个生产者发起事务回查请求
private String producerGroup;
//默认topic
private String createTopicKey = MixAll.AUTO_CREATE_TOPIC_KEY_TOPIC;
//默认主题在每一个Broker队列数量
private volatile int defaultTopicQueueNums = 4;
//发送消息默认超时时间,默认3秒
private int sendMsgTimeout = 3000;
//消息体超过该值则启用压缩,默认4K
private int compressMsgBodyOverHowmuch = 1024 * 4;
//同步方式发送消息重试次数,默认为2,总共执行三次。
private int retryTimesWhenSendFailed = 2;
//异步方式发送消息重试次数,默认为2.
private int retryTimesWhenSendAsyncFailed = 2;
//消息重试时选择另外一个broker时,是否不等待结果就返回,默认为false。
private boolean retryAnotherBrokerWhenNotStoreOK = false;
//允许发送消息的最大长度,默认为4M,该值最大为2^32-1
private int maxMessageSize = 1024 * 1024 * 4; // 4M
private TraceDispatcher traceDispatcher = null;
}
2、消息生产者启动流程:
a、检查productGroup是否符合要求,并改变生产者的instanceName为进程ID。
b、创建MQClientInstance实例,整个jvm实例中只存在一个MQClientInstance实例,维护一个MQClientInstance缓存表,也就是同一个ClientID只会去创建一个MQClientInstance。【clientId为客户端IP+Instance+(unitname)】

c、向MQClientInstance注册,将当前生产者加入到,方便后续调用网络请求、进行心跳检测等。
d、启动MQClientInstance,如果MQClientInstance已经启动,则本次启动不会真正执行。
四、消息发送基本流程
消息发送的流程主要包括:验证消息、查找路由、消息发送(包含异常处理机制)。
默认消息发送以同步方式推送,默认超时时间为3秒。
1、消息长度验证:
消息发送之前,首先确认生产者处于运行状态,然后验证消息是否符合响应的规范,具体的规范要求是主题名称,消息体不能为空、消息长度不能等于0且默认不能超过允许发送消息的最大长度4M(1024*1024*4)。
2、查找主题路由消息:
消息发送之前,首先需要获取主题的路由信息,只有获取了这些信息,我们才能知道消息要发送的具体的Broker节点。
DefaultMQProducerImpl的tryToFindPublisherInfo方法是查找主题路由信息的方法,如果生产者中缓存了topic的路由信息,
如果该路由信息包含了消息队列,则直接返回该路由信息,如果没有缓存或者没有包含消息队列,则向NameServer查询该topic的路由信息,如果最终未找到路由信息,则抛出异常。

相关参数解析:
orderTopic:是否是顺序消息;
List<MessageQueue> messageQueueList : 该主题队列的消息队列;
sendWhichQueue :每选择一次消息队列,该值会自增1,如果Integer.MAX_VALUE,则重置为0 ,用于选择消息队列。
List<QueueData> queueData: topic队列元数据;
List<BrokerData> brokerDatas : topic分布的broker 元数据;
HashMap<String brokerAddress, List<String > filterServer> broker :broker上过滤服务器地址列表。
第一次发送消息时,本地没有缓存Topic的路由信息,查询NameServer尝试获取,如果路由信息未找到,再次尝试使用DefaultMQProducerImpl#createTopicKey去查询,如果BrokerConfig#autoCreateTopicEnable为True时,NameServer将返回路由信息,如果为false则抛出无法找到topic路由异常,MQClientInstance#updateTopicRouteInfoFromNameServer这个方法的功能是消息生产者更新和维护路由缓存。




3、如何选择消息队列:

public MessageQueue selectOneMessageQueue(final String lastBrokerName) {
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;
MessageQueue mq = this.messageQueueList.get(pos);
if (!mq.getBrokerName().equals(lastBrokerName)) {
return mq;
}
}
return selectOneMessageQueue();
}
}
public MessageQueue selectOneMessageQueue() {
int index = this.sendWhichQueue.getAndIncrement();
int pos = Math.abs(index) % this.messageQueueList.size();
if (pos < 0)
pos = 0;
return this.messageQueueList.get(pos);
}

Broker故障延迟机制:
public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName) {
if (this.sendLatencyFaultEnable) {
try {
int index = tpInfo.getSendWhichQueue().getAndIncrement();
for (int i = 0; i < tpInfo.getMessageQueueList().size(); i++) {
int pos = Math.abs(index++) % tpInfo.getMessageQueueList().size();
if (pos < 0)
pos = 0;
MessageQueue mq = tpInfo.getMessageQueueList().get(pos);
if (latencyFaultTolerance.isAvailable(mq.getBrokerName())) {
if (null == lastBrokerName || mq.getBrokerName().equals(lastBrokerName))
return mq;
}
}
final String notBestBroker = latencyFaultTolerance.pickOneAtLeast();
int writeQueueNums = tpInfo.getQueueIdByBroker(notBestBroker);
if (writeQueueNums > 0) {
final MessageQueue mq = tpInfo.selectOneMessageQueue();
if (notBestBroker != null) {
mq.setBrokerName(notBestBroker);
mq.setQueueId(tpInfo.getSendWhichQueue().getAndIncrement() % writeQueueNums);
}
return mq;
} else {
latencyFaultTolerance.remove(notBestBroker);
}
} catch (Exception e) {
log.error("Error occurred when selecting message queue", e);
}
return tpInfo.selectOneMessageQueue();
}
return tpInfo.selectOneMessageQueue(lastBrokerName);
}





障规避时长。

public void updateFaultItem(final String name, final long currentLatency, final long notAvailableDuration) {
FaultItem old = this.faultItemTable.get(name);
if (null == old) {
final FaultItem faultItem = new FaultItem(name);
faultItem.setCurrentLatency(currentLatency);
faultItem.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration);
old = this.faultItemTable.putIfAbsent(name, faultItem);
if (old != null) {
old.setCurrentLatency(currentLatency);
old.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration);
}
} else {
old.setCurrentLatency(currentLatency);
old.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration);
}
}

4、消息发送:
消息发送API核心入口:org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendKernelImpl
private SendResult sendKernelImpl(final Message msg,
final MessageQueue mq,
final CommunicationMode communicationMode,
final SendCallback sendCallback,
final TopicPublishInfo topicPublishInfo,
final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
long beginStartTime = System.currentTimeMillis();
String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());
if (null == brokerAddr) {
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
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;
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)) {
sysFlag |= MessageSysFlag.TRANSACTION_PREPARED_TYPE;
}
if (hasCheckForbiddenHook()) {
CheckForbiddenContext checkForbiddenContext = new CheckForbiddenContext();
checkForbiddenContext.setNameSrvAddr(this.defaultMQProducer.getNamesrvAddr());
checkForbiddenContext.setGroup(this.defaultMQProducer.getProducerGroup());
checkForbiddenContext.setCommunicationMode(communicationMode);
checkForbiddenContext.setBrokerAddr(brokerAddr);
checkForbiddenContext.setMessage(msg);
checkForbiddenContext.setMq(mq);
checkForbiddenContext.setUnitMode(this.isUnitMode());
this.executeCheckForbiddenHook(checkForbiddenContext);
}
if (this.hasSendMessageHook()) {
context = new SendMessageContext();
context.setProducer(this);
context.setProducerGroup(this.defaultMQProducer.getProducerGroup());
context.setCommunicationMode(communicationMode);
context.setBornHost(this.defaultMQProducer.getClientIP());
context.setBrokerAddr(brokerAddr);
context.setMessage(msg);
context.setMq(mq);
context.setNamespace(this.defaultMQProducer.getNamespace());
String isTrans = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
if (isTrans != null && isTrans.equals("true")) {
context.setMsgType(MessageType.Trans_Msg_Half);
}
if (msg.getProperty("__STARTDELIVERTIME") != null || msg.getProperty(MessageConst.PROPERTY_DELAY_TIME_LEVEL) != null) {
context.setMsgType(MessageType.Delay_Msg);
}
this.executeSendMessageHookBefore(context);
}
SendMessageRequestHeader requestHeader = new SendMessageRequestHeader();
requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());
requestHeader.setTopic(msg.getTopic());
requestHeader.setDefaultTopic(this.defaultMQProducer.getCreateTopicKey());
requestHeader.setDefaultTopicQueueNums(this.defaultMQProducer.getDefaultTopicQueueNums());
requestHeader.setQueueId(mq.getQueueId());
requestHeader.setSysFlag(sysFlag);
requestHeader.setBornTimestamp(System.currentTimeMillis());
requestHeader.setFlag(msg.getFlag());
requestHeader.setProperties(MessageDecoder.messageProperties2String(msg.getProperties()));
requestHeader.setReconsumeTimes(0);
requestHeader.setUnitMode(this.isUnitMode());
requestHeader.setBatch(msg instanceof MessageBatch);
if (requestHeader.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
String reconsumeTimes = MessageAccessor.getReconsumeTime(msg);
if (reconsumeTimes != null) {
requestHeader.setReconsumeTimes(Integer.valueOf(reconsumeTimes));
MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_RECONSUME_TIME);
}
String maxReconsumeTimes = MessageAccessor.getMaxReconsumeTimes(msg);
if (maxReconsumeTimes != null) {
requestHeader.setMaxReconsumeTimes(Integer.valueOf(maxReconsumeTimes));
MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_MAX_RECONSUME_TIMES);
}
}
SendResult sendResult = null;
switch (communicationMode) {
case ASYNC:
Message tmpMessage = msg;
boolean messageCloned = false;
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;
case ONEWAY:
case SYNC:
long costTimeSync = System.currentTimeMillis() - beginStartTime;
if (timeout < costTimeSync) {
throw new RemotingTooMuchRequestException("sendKernelImpl call timeout");
}
sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage(
brokerAddr,
mq.getBrokerName(),
msg,
requestHeader,
timeout - costTimeSync,
communicationMode,
context,
this);
break;
default:
assert false;
break;
}
if (this.hasSendMessageHook()) {
context.setSendResult(sendResult);
this.executeSendMessageHookAfter(context);
}
return sendResult;
} catch (RemotingException e) {
if (this.hasSendMessageHook()) {
context.setException(e);
this.executeSendMessageHookAfter(context);
}
throw e;
} catch (MQBrokerException e) {
if (this.hasSendMessageHook()) {
context.setException(e);
this.executeSendMessageHookAfter(context);
}
throw e;
} catch (InterruptedException e) {
if (this.hasSendMessageHook()) {
context.setException(e);
this.executeSendMessageHookAfter(context);
}
throw e;
} finally {
msg.setBody(prevBody);
msg.setTopic(NamespaceUtil.withoutNamespace(msg.getTopic(), this.defaultMQProducer.getNamespace()));
}
}
throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null);
}

本文围绕RocketMQ展开,介绍了其消息发送的三种方式,包括同步、异步、单向。阐述了消息的属性,如主题、扩展属性等。还讲解了生产者启动流程,以及消息发送的基本流程,涵盖消息验证、路由查找、队列选择和消息发送等关键环节。
459

被折叠的 条评论
为什么被折叠?



