首先以默认的异步消息发送模式作为例子。DefaultMQProducer中的send()方法会直接调用DefaultMQProducerImpl的send()方法,在DefaultMQProducerImpl会直接调用sendDefaultImpl()方法。
public void send(Message msg, SendCallback sendCallback) throws MQClientException, RemotingException,
InterruptedException {
send(msg, sendCallback, this.defaultMQProducer.getSendMsgTimeout());
}
public void send(Message msg, SendCallback sendCallback, long timeout) throws MQClientException,
RemotingException, InterruptedException {
try {
this.sendDefaultImpl(msg, CommunicationMode.ASYNC, sendCallback, timeout);
}
catch (MQBrokerException e) {
throw new MQClientException("unknown exception", e);
}
}
private SendResult sendDefaultImpl(//
Message msg,//
final CommunicationMode communicationMode,//
final SendCallback sendCallback, final long timeout//
) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
this.makeSureStateOK();
Validators.checkMessage(msg, this.defaultMQProducer);
final long maxTimeout = this.defaultMQProducer.getSendMsgTimeout() + 1000;
final long beginTimestamp = System.currentTimeMillis();
long endTimestamp = beginTimestamp;
TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());
if (topicPublishInfo != null && topicPublishInfo.ok()) {
MessageQueue mq = null;
Exception exception = null;
SendResult sendResult = null;
int timesTotal = 1 + this.defaultMQProducer.getRetryTimesWhenSendFailed();
int times = 0;
String[] brokersSent = new String[timesTotal];
for (; times < timesTotal && (endTimestamp - beginTimestamp) < maxTimeout; times++) {
String lastBrokerName = null == mq ? null : mq.getBrokerName();
MessageQueue tmpmq = topicPublishInfo.selectOneMessageQueue(lastBrokerName);
if (tmpmq != null) {
mq = tmpmq;
brokersSent[times] = mq.getBrokerName();
try {
sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, timeout);
endTimestamp = System.currentTimeMillis();
switch (communicationMode) {
case ASYNC:
return null;
case ONEWAY:
return null;
case SYNC:
if (sendResult.getSendStatus() != SendStatus.SEND_OK) {
if (this.defaultMQProducer.isRetryAnotherBrokerWhenNotStoreOK()) {
continue;
}
}
return sendResult;
default:
break;
}
}
catch (RemotingException e) {
log.warn("sendKernelImpl exception", e);
log.warn(msg.toString());
exception = e;
endTimestamp = System.currentTimeMillis();
continue;
}
catch (MQClientException e) {
log.warn("sendKernelImpl exception", e);
log.warn(msg.toString());
exception = e;
endTimestamp = System.currentTimeMillis();
continue;
}
catch (MQBrokerException e) {
log.warn("sendKernelImpl exception", e);
log.warn(msg.toString());
exception = e;
endTimestamp = System.currentTimeMillis();
switch (e.getResponseCode()) {
case ResponseCode.TOPIC_NOT_EXIST:
case ResponseCode.SERVICE_NOT_AVAILABLE:
case ResponseCode.SYSTEM_ERROR:
case ResponseCode.NO_PERMISSION:
case ResponseCode.NO_BUYER_ID:
case ResponseCode.NOT_IN_CURRENT_UNIT:
continue;
default:
if (sendResult != null) {
return sendResult;
}
throw e;
}
}
catch (InterruptedException e) {
log.warn("sendKernelImpl exception", e);
log.warn(msg.toString());
throw e;
}
}
else {
break;
}
} // end of for
if (sendResult != null) {
return sendResult;
}
String info =
String.format("Send [%d] times, still failed, cost [%d]ms, Topic: %s, BrokersSent: %s", //
times, //
(System.currentTimeMillis() - beginTimestamp), //
msg.getTopic(),//
Arrays.toString(brokersSent));
info += FAQUrl.suggestTodo(FAQUrl.SEND_MSG_FAILED);
throw new MQClientException(info, exception);
}
List nsList = this.getmQClientFactory().getMQClientAPIImpl().getNameServerAddressList();
if (null == nsList || nsList.isEmpty()) {
throw new MQClientException("No name server address, please set it."
+ FAQUrl.suggestTodo(FAQUrl.NAME_SERVER_ADDR_NOT_EXIST_URL), null);
}
throw new MQClientException("No route info of this topic, " + msg.getTopic()
+ FAQUrl.suggestTodo(FAQUrl.NO_TOPIC_ROUTE_INFO), null);
}
由该方法的参数可得知Rocketmq的消息发送模式,打开CommunicationMode可以看到具体的发送模式
public enum CommunicationMode {
SYNC,
ASYNC,
ONEWAY,
}
以异步消息发送模式(ASYNC)作为例子,需要在具体的实现里传入相应的sendCallback处理消息异步收到消息回复结果的消息处理。
首先通过调用makeSureStateOK()方法来确保该生产者正处于运行状态,判断的方式很简单
private void makeSureStateOK() throws MQClientException {
if (this.serviceState != ServiceState.RUNNING) {
throw new MQClientException("The producer service state not OK, "//
+ this.serviceState//
+ FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK), null);
}
}
只要比较一下状态量就行了。
接下来是对所需要的发送的消息进行验证,具体的验证方法在RocketMq里的Validators实现
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: " + defaultMQProducer.getMaxMessageSize());
}
}
具体是对消息具体数据的判空,以及在通过和在DefaultMQProducer里的消息大小配置属性进行比较确保消息的大小符合在配置中的设置。在其中也对消息的topic进行了检察,主要通过正则表达式确保topic的格式,以及topic的有效性。
在接下来的发送步骤中,接下里通过调用tryTOFindTopicPublishInfo()方法来根据消息的topic来获取相关topicd的路由信息。这个时候,整个发送消息的beginTimestamp已经被设置,也就是说整个发送消息的timeout已经开始。
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 != null && topicPublishInfo.ok())) {
return topicPublishInfo;
}
else {
this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic, true, this.defaultMQProducer);
topicPublishInfo = this.topicPublishInfoTable.get(topic);
return topicPublishInfo;
}
}
在这个方法中,首先会尝试在DefaultMQProducerImpl中保存的路由信息map里去寻找,如果找不到则会重新创建一个topicPublishInfo类,从名称服务那里去尝试更新获取新的路由信息数据。这里调用的方法updateTopicPublishInfo()方法与客户端执行的定时更新路由任务相同,也就说在发送topic找不到相应的路由信息的时候会重复一次更新这个定时任务的操作。
在成功获取了相应路由信息的同时就可以正式开始消息的发送。在之前的尝试获取路由消息的步骤已经算在了整个消息发送的timeout里。
在整个消息发送的过程中,如果因为各种原因消息发送失败,可以设置消息重新发送的次数。也就是说一短消息最大可以发送的次数是1+最大可重发次数,建立一个该次数大小的定长数组来保存每次发送的brokerName。这里主要针对同步发送模式。
首先通过之前获得topic得到的路由信息来以上一次发送的BrokerName为依据得到当前次数发送的消息队列。
由topicPublishInfo的selectOneMessageQuene()方法来实现。
public MessageQueue selectOneMessageQueue(final String lastBrokerName) {
if (lastBrokerName != null) {
int index = this.sendWhichQueue.getAndIncrement();
for (int i = 0; i < this.messageQueueList.size(); i++) {
int pos = Math.abs(index++) % this.messageQueueList.size();
MessageQueue mq = this.messageQueueList.get(pos);
if (!mq.getBrokerName().equals(lastBrokerName)) {
return mq;
}
}
return null;
}
else {
int index = this.sendWhichQueue.getAndIncrement();
int pos = Math.abs(index) % this.messageQueueList.size();
return this.messageQueueList.get(pos);
}
}
消息队列的选取在这里根据消息传进来的时候的index达到平均轮询各个消息队列的目的,也就是说完成了每个消息队列负载的平衡,与此同时,可以根据上一次发送的broker名称达到不在一条消息队列重复发送的目的。
在成功获取了需要发送的消息队列之后,调用sendKernalImpl()发送消息。
private SendResult sendKernelImpl(final Message msg,//
final MessageQueue mq,//
final CommunicationMode communicationMode,//
final SendCallback sendCallback,//
final long timeout) throws MQClientException, RemotingException, MQBrokerException,
InterruptedException {
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) {
if(this.defaultMQProducer.isSendMessageWithVIPChannel()) {
brokerAddr = MixAll.brokerVIPChannel(brokerAddr);
}
byte[] prevBody = msg.getBody();
try {
int sysFlag = 0;
if (this.tryToCompressMessage(msg)) {
sysFlag |= MessageSysFlag.CompressedFlag;
}
final String tranMsg = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
if (tranMsg != null && Boolean.parseBoolean(tranMsg)) {
sysFlag |= MessageSysFlag.TransactionPreparedType;
}
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.setProducerGroup(this.defaultMQProducer.getProducerGroup());
context.setCommunicationMode(communicationMode);
context.setBornHost(this.defaultMQProducer.getClientIP());
context.setBrokerAddr(brokerAddr);
context.setMessage(msg);
context.setMq(mq);
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());
if (requestHeader.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
String reconsumeTimes = MessageAccessor.getReconsumeTime(msg);
if (reconsumeTimes != null) {
requestHeader.setReconsumeTimes(new Integer(reconsumeTimes));
MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_RECONSUME_TIME);
}
}
SendResult sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage(//
brokerAddr,// 1
mq.getBrokerName(),// 2
msg,// 3
requestHeader,// 4
timeout,// 5
communicationMode,// 6
sendCallback// 7
);
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);
}
}
throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null);
}
首先,根据当前的BorkerName从本地的Broker地址缓存中获取相应的地址,如果找不到,跟之前的方式一样,重新跟名称服务更新新的路由信息。
接下来根据之前的DefaultProducer配置类对具体的方式方式进行配置。
如果在一开始配置了高优先级队列,则在这里就会选择高优先级队列。
在这里给出一个sysFlag标志位。
之后进行压缩处理,如果所要发送消息的body部分超过了配置类需要压缩的大小。
private boolean tryToCompressMessage(final Message msg) {
byte[] body = msg.getBody();
if (body != null) {
if (body.length >= this.defaultMQProducer.getCompressMsgBodyOverHowmuch()) {
try {
byte[] data = UtilAll.compress(body, zipCompressLevel);
if (data != null) {
msg.setBody(data);
return true;
}
}
catch (IOException e) {
log.error("tryToCompressMessage exception", e);
log.warn(msg.toString());
}
}
}
return false;
}
在压缩中,具体采用了zip压缩方式。
如果在此处的确采用了压缩,则给标志量低一位为1。
public final static int CompressedFlag = (0x1 << 0);
接下来,若果该消息属于事务消息,也会给相应的标志量赋值,这里暂时不展开。
在接下里,如果该生产者配置了相关的注册了chackForbiddenHook,则在这里将会走一遍所有的注册了的checkForbidden钩子保证本来配置被禁发的消息不会被发送出去。
类似的,在接下里如果跟之前的钩子一样的方式配置注册了sendMessageHook消息发送钩子,则会在这里遍历调用所有钩子的executesendMessageHookBefore()方法,相应的,在消息发送完毕之后也会 执行executeSendMessageHookAfter()方法。
之后根据之前得到的一系列发送消息的配置,来构造发送给Broker的请求头数据。
在一切准备就绪之后,调用客户端的API接口来实现消息的物理发送。
SendResult sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage(//
brokerAddr,// 1
mq.getBrokerName(),// 2
msg,// 3
requestHeader,// 4
timeout,// 5
communicationMode,// 6
sendCallback// 7
);
如果采用了ASYNC的异步发送模式,则这个最后一个参数就是在消息发送之后用来处理消息回复的类。
public SendResult sendMessage(//
final String addr,// 1
final String brokerName,// 2
final Message msg,// 3
final SendMessageRequestHeader requestHeader,// 4
final long timeoutMillis,// 5
final CommunicationMode communicationMode,// 6
final SendCallback sendCallback// 7
) throws RemotingException, MQBrokerException, InterruptedException {
RemotingCommand request = null;
if (sendSmartMsg) {
SendMessageRequestHeaderV2 requestHeaderV2 = SendMessageRequestHeaderV2.createSendMessageRequestHeaderV2(requestHeader);
request = RemotingCommand.createRequestCommand(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:
this.sendMessageAsync(addr, brokerName, msg, timeoutMillis, request, sendCallback);
return null;
case SYNC:
return this.sendMessageSync(addr, brokerName, msg, timeoutMillis, request);
default:
assert false;
break;
}
return null;
}
以异步方式为例
private void sendMessageAsync(//
final String addr,//
final String brokerName,//
final Message msg,//
final long timeoutMillis,//
final RemotingCommand request,//
final SendCallback sendCallback//
) throws RemotingException, InterruptedException {
this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() {
@Override
public void operationComplete(ResponseFuture responseFuture) {
if (null == sendCallback)
return;
RemotingCommand response = responseFuture.getResponseCommand();
if (response != null) {
try {
SendResult sendResult = MQClientAPIImpl.this.processSendResponse(brokerName, msg, response);
assert sendResult != null;
sendCallback.onSuccess(sendResult);
}
catch (Exception e) {
sendCallback.onException(e);
}
}
else {
if (!responseFuture.isSendRequestOK()) {
sendCallback.onException(new MQClientException("send request failed", responseFuture.getCause()));
}
else if (responseFuture.isTimeout()) {
sendCallback.onException(new MQClientException("wait response timeout " + responseFuture.getTimeoutMillis() + "ms",
responseFuture.getCause()));
}
else {
sendCallback.onException(new MQClientException("unknow reseaon", responseFuture.getCause()));
}
}
}
});
}
在这里会根据传入的SendCallBack对象生成相应的responseFuture任务类交由netty客户端来处理。
public void invokeAsyncImpl(final Channel channel, final RemotingCommand request,
final long timeoutMillis, final InvokeCallback invokeCallback) throws InterruptedException,
RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException {
boolean acquired = this.semaphoreAsync.tryAcquire(timeoutMillis, TimeUnit.MILLISECONDS);
if (acquired) {
final SemaphoreReleaseOnlyOnce once = new SemaphoreReleaseOnlyOnce(this.semaphoreAsync);
final ResponseFuture responseFuture =
new ResponseFuture(request.getOpaque(), timeoutMillis, invokeCallback, once);
this.responseTable.put(request.getOpaque(), responseFuture);
try {
channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture f) throws Exception {
if (f.isSuccess()) {
responseFuture.setSendRequestOK(true);
return;
}
else {
responseFuture.setSendRequestOK(false);
}
responseFuture.putResponse(null);
responseTable.remove(request.getOpaque());
try {
responseFuture.executeInvokeCallback();
}
catch (Throwable e) {
plog.warn("excute callback in writeAndFlush addListener, and callback throw", e);
}
finally {
responseFuture.release();
}
plog.warn("send a request command to channel <{}> failed.",
RemotingHelper.parseChannelRemoteAddr(channel));
plog.warn(request.toString());
}
});
}
catch (Exception e) {
responseFuture.release();
plog.warn(
"send a request command to channel <" + RemotingHelper.parseChannelRemoteAddr(channel)
+ "> Exception", e);
throw new RemotingSendRequestException(RemotingHelper.parseChannelRemoteAddr(channel), e);
}
}
else {
if (timeoutMillis <= 0) {
throw new RemotingTooMuchRequestException("invokeAsyncImpl invoke too fast");
}
else {
String info =
String
.format(
"invokeAsyncImpl tryAcquire semaphore timeout, %dms, waiting thread nums: %d semaphoreAsyncValue: %d", //
timeoutMillis,//
this.semaphoreAsync.getQueueLength(),//
this.semaphoreAsync.availablePermits()//
);
plog.warn(info);
plog.warn(request.toString());
throw new RemotingTimeoutException(info);
}
}
}
可以看到,生成的responseFuture被netty远程客户端管理在map里,动态实现了在收到消息回复之后调用的operationCompleted()方法,将根据消息结果的异步返回调用相应的鄂onSuccess()或者onException()方法,来完成ASYNC异步的目的。
在这里,如果是异步的将直接返回,由上面的方式完成之后消息回复的处理。到这里RockerMQ异步发送的步骤正式宣告结束。
而ONEWAY单向消息发送模式在发送完毕消息后马上会结束,并不会管消息发送的结果。
如果是SYNC同步消息发送模式,如果消息发送失败,则会选择另一个BrokerName来尝试继续发送,直到retry次数用尽。当然顾名思义,在同步消息模式的消息发送后,将会等待结果并调用客户端API接口实现的processSendResponse()方法来处理结果。