1 概述
将发送消息的过程逻辑分为三层:应用层,传输层,网络层
client
/DefaultMQProducer
/DefaultMQProducerImpl
:这三者中的逻辑属于应用层的处理逻辑,主要逻辑就是处理消息本身相关的事情MQClientAPIImpl
:此类中的逻辑归属于传输层,应用层与网络层之间的桥梁,主要的逻辑是将应用层的结果封装为网络层协议RemotingCommand
以及消息头Header
NettyRemotingClient
:网络层,处理通信相关的通信
整个同步发送消息过程的时序图如下所示:
2 应用层消息发送
应用层代码使用RocketMQ生产者客户端DefaultMQProducer
的send()
方法发送消息。首先是检查消息相关的信息,不能为空,主题格式,消息体等。
@Override
public SendResult send(
Message msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
// 检查消息格式: 消息不能为空
// 主题不能为空,长度,格式(数字、字母)
// body不能为空,0<长度<最大值
Validators.checkMessage(msg, this);
// 给主题加上命名空间
msg.setTopic(withNamespace(msg.getTopic()));
// 调用内部实现发送消息
return this.defaultMQProducerImpl.send(msg);
}
接着调用RocketMQ内部客户端DefaultMQProducerImpl
的send()
方法。并设置了发送消息的超时时间。
/**
* DEFAULT SYNC -------------------------------------------------------
*/
public SendResult send(
Message msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
// 同步发送的默认超时时间为3000
return send(msg, this.defaultMQProducer.getSendMsgTimeout());
}
设置通信模式为同步方式,设置回调函数为null,因为为同步发送。
public SendResult send(Message msg,
long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
// 这里显式的设置了为同步发送的方式,设置发送回调函数为空
return this.sendDefaultImpl(msg, CommunicationMode.SYNC, null, timeout);
}
接着调用sendDefaultImpl()
方法,在此方法中主要做了:
- 查找路由信息
- 使用故障容错组件选择消息队列。
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);
// 生成一个调用ID,后面ResponseFuture对应的Opaque
final long invokeID = random.nextLong();
// 开始时间戳
long beginTimestampFirst = System.currentTimeMillis();
long beginTimestampPrev = beginTimestampFirst;
long endTimestamp = beginTimestampFirst;
// 获取到路由信息 TODO
// @【1】 查找路由信息
TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());
// 只在有路由信息的时候,且路由信息正常(有消息队列)
if (topicPublishInfo != null && topicPublishInfo.ok()) {
boolean callTimeout = false;
MessageQueue mq = null;
Exception exception = null;
SendResult sendResult = null;
// 次数,同步=重试次数+1,异步=1,为何这样设置? 因为异步的重试不在这里
int timesTotal = communicationMode == CommunicationMode.SYNC ? 1 + this.defaultMQProducer.getRetryTimesWhenSendFailed() : 1;
// 计数器
int times = 0;
// 已经发送了的broker
String[] brokersSent = new String[timesTotal];
// 重试
for (; times < timesTotal; times++) {
// 选择的broker
String lastBrokerName = null == mq ? null : mq.getBrokerName();
// 根据broker选择的消息队列 TODO
// @【2】根据路由信息和Broker选择消息队列
MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);
if (mqSelected != null) {
mq = mqSelected;
// 设置当前发送的broker
brokersSent[times] = mq.getBrokerName();
try {
// 开始时间
beginTimestampPrev = System.currentTimeMillis();
// 如果重试次数大于0,表明已经重试了
if (times > 0) {
//Reset topic with namespace during resend.
// 为什么要重新设置呢? 因为在发送消息的时候会改变消息的主题 TODO
msg.setTopic(this.defaultMQProducer.withNamespace(msg.getTopic()));
}
// 已经用了的时间
long costTime = beginTimestampPrev - beginTimestampFirst;
// 超时了,就不继续了,直接退出循环,也就是讲重试必须在设置的超时时间以内才重新发送
if (timeout < costTime) {
callTimeout = true;
break;
}
// 下一个发送逻辑,这里传进去的超时时间已经减去了已经消耗了的时间 TODO
// @【3】找到后消息队列和路由信息后处理
sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout - costTime);
// 结束时间戳
endTimestamp = System.currentTimeMillis();
// 更新故障容错
// @④ 没有出现异常时的更新故障容错
this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false);
// 根据不同的发送方式返回不同的结果
// 异步和单向直接返回null
switch (communicationMode) {
case ASYNC:
return null;
case ONEWAY:
return null;
case SYNC:
// 如果返回的结果不是OK的话且能重试那么就重试,如果得到的结果不是 SEND_OK
// 没有返回结果时,比如超时了,那么此时就直接进行重试
if (sendResult.getSendStatus() != SendStatus.SEND_OK) {
// 当返回码不是发送成功,但是broker仍然返回了结果,此时是否需要重试?
if (this.defaultMQProducer.isRetryAnotherBrokerWhenNotStoreOK()) {
continue;
}
}
// 如果不重试的话直接返回结果了
return sendResult;
// 如果通信模式是其他,那么直接返回
default:
break;
}
} catch (RemotingException e) {
endTimestamp = System.currentTimeMillis();
// 更新故障容错
this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, true);
log.warn(String.format("sendKernelImpl exception, resend at once, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq), e);
log.warn(msg.toString());
exception = e;
continue;
} catch (MQClientException e) {
endTimestamp = System.currentTimeMillis();
this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, true);
log.warn(String.format("sendKernelImpl exception, resend at once, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq), e);
log.warn(msg.toString());
exception = e;
continue;
} catch (MQBrokerException e) {
endTimestamp = System.currentTimeMillis();
this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, true);
log.warn(String.format("sendKernelImpl exception, resend at once, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq), e);
log.warn(msg.toString());
exception = e;
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) {
endTimestamp = System.currentTimeMillis();
this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false);
log.warn(String.format("sendKernelImpl exception, throw exception, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq), e);
log.warn(msg.toString());
log.warn("sendKernelImpl exception", e);
log.warn(msg.toString());
throw e;
}
} else {
// 如果选择到的消息队列为空,那么直接退出循环
break;
}
}
//
if (sendResult != null) {
return sendResult;
}
// 重试仍然失败
String info = String.format("Send [%d] times, still failed, cost [%d]ms, Topic: %s, BrokersSent: %s",
times,
System.currentTimeMillis() - beginTimestampFirst,
msg.getTopic(),
Arrays.toString(brokersSent));
info += FAQUrl.suggestTodo(FAQUrl.SEND_MSG_FAILED);
MQClientException mqClientException = new MQClientException(info, exception);
// 如果超时了就抛出异常
if (callTimeout) {
throw new RemotingTooMuchRequestException("sendDefaultImpl call timeout");
}
// 出现其他异常的情况
if (exception instanceof MQBrokerException) {
mqClientException.setResponseCode(((MQBrokerException) exception).getResponseCode());
} else if (exception instanceof RemotingConnectException) {
mqClientException.setResponseCode(ClientErrorCode.CONNECT_BROKER_EXCEPTION);
} else if (exception instanceof RemotingTimeoutException) {
mqClientException.setResponseCode(ClientErrorCode.ACCESS_BROKER_TIMEOUT);
} else if (exception instanceof MQClientException) {
mqClientException.setResponseCode(ClientErrorCode.BROKER_NOT_EXIST_EXCEPTION);
}
throw mqClientException;
}
validateNameServerSetting();
throw new MQClientException("No route info of this topic: " + msg.getTopic() + FAQUrl.suggestTodo(FAQUrl.NO_TOPIC_ROUTE_INFO),
null).setResponseCode(ClientErrorCode.NOT_FOUND_TOPIC_EXCEPTION);
}
2.1 查找路由信息
这里主要先是从路由表中获取主题的路由信息,如果没有获取到就尝试更新一下路由信息。如果还是没有获取到那么就使用默认主题的消息队列。
private TopicPublishInfo tryToFindTopicPublishInfo(final String topic) {
// 从路由维护信息表中(根据主题)获取路由信息
// ConcurrentMap<String/* topic */, TopicPublishInfo>
TopicPublishInfo topicPublishInfo = this.topicPublishInfoTable.get(topic);
// 如果不存在主题的路由信息或者不OK(消息队列为空)
// 则尝试进行更新路由信息
if (null == topicPublishInfo || !topicPublishInfo.ok()) {
this.topicPublishInfoTable.putIfAbsent(topic, new TopicPublishInfo());
// 调用ClientInstance进行主题路由信息的更新,根据主题更新主题下的路由信息
this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic);
// 再一次获取
topicPublishInfo = this.topicPublishInfoTable.get(topic);
}
if (topicPublishInfo.isHaveTopicRouterInfo() || topicPublishInfo.ok()) {
return topicPublishInfo;
} // 还是没有可用的路由信息时
else {
// 再次更新,此时使用默认主题["TBW102"]进行更新,如果主题不存在则创建主题
// 使用默认主题的路由信息来表示该主题的路由信息
this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic, true, this.defaultMQProducer);
// 重新获取路由信息
topicPublishInfo = this.topicPublishInfoTable.get(topic);
return topicPublishInfo;
}
}
2.2 选择消息队列
如果开启了故障容错则调用故障使用故障容错的方式获取一个消息队列。否则就是普通的方式获取。
具体的分析在故障容错里面进行分析。
具体的结果就是选择一个可以发送消息的MessageQueue
public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName) {
// 调用故障容错选择消息队列
return this.mqFaultStrategy.selectOneMessageQueue(tpInfo, lastBrokerName);
}
3 传输层发送消息
主要逻辑:
- 找到Broker的地址
- 构建传输层协议头
SendMessageRequestHeader
// 后面的发送消息都会到这里来
// 与MQClientAPIImpl(网络层)交互的接口
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();
// 【1】根据broker的名字找到broker的地址
String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());
// 如果broker的地址为空
if (null == brokerAddr) {
// 那么尝试一下更新主题的路由信息
tryToFindTopicPublishInfo(mq.getTopic());
// 再次获取Broker的Addr
brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());
}
SendMessageContext context = null;
// 只有获取到了brokerAddr时才继续往下运行
if (brokerAddr != null) {
// 根据是否使用VIP包裹VIP通道 TODO VIP 通道的使用是怎样的???
brokerAddr = MixAll.brokerVIPChannel(this.defaultMQProducer.isSendMessageWithVIPChannel(), brokerAddr);
// 获取消息体的字节流,备份的作用
byte[] prevBody = msg.getBody();
try {
//for MessageBatch,ID has been set in the generating process
// 给消息设置唯一ID,即添加一个property:UNIQ_KEY
if (!(msg instanceof MessageBatch)) {
MessageClientIDSetter.setUniqID(msg);
}
boolean topicWithNamespace = false;
if (null != this.mQClientFactory.getClientConfig().getNamespace()) {
// 设置instanceId property = namespace
msg.setInstanceId(this.mQClientFactory.getClientConfig().getNamespace());
topicWithNamespace = true;
}
// 系统标志为0
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);
}
// 生成Header,RocketMQ中的做法
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);
// 如果主题是以重试开头? 即"%RETRY%"
if (requestHeader.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
// "RECONSUME_TIME" 重复消费次数?
String reconsumeTimes = MessageAccessor.getReconsumeTime(msg);
if (reconsumeTimes != null) {
// 在消息头设置重复消费次数?TODO 这个重复消费次数有什么作用?
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));
// 清除property
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;
}
// 异步发送的时候在这里去掉了消息nameSpace
msg.setTopic(NamespaceUtil.withoutNamespace(msg.getTopic(), this.defaultMQProducer.getNamespace()));
}
// 已经消耗的时间
long costTimeAsync = System.currentTimeMillis() - beginStartTime;
if (timeout < costTimeAsync) {
throw new RemotingTooMuchRequestException("sendKernelImpl call timeout");
}
// 调用客户端API进行发送消息
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()));
}
}
// 如果找不到broker的地址,那么就直接抛出异常
throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null);
}
public SendResult sendMessage(
final String addr,
final String brokerName,
final Message msg,
final SendMessageRequestHeader requestHeader,
final long timeoutMillis,
final CommunicationMode communicationMode,
final SendMessageContext context,
final DefaultMQProducerImpl producer
) throws RemotingException, MQBrokerException, InterruptedException {
return sendMessage(addr, brokerName, msg, requestHeader, timeoutMillis, communicationMode, null, null, null, 0, context, producer);
}
构建网络层协议对象RemotingCommand
对象。
public SendResult sendMessage(
final String addr,
final String brokerName,
final Message msg,
final SendMessageRequestHeader requestHeader,
final long timeoutMillis,
final CommunicationMode communicationMode,
final SendCallback sendCallback,
final TopicPublishInfo topicPublishInfo,
final MQClientInstance instance,
final int retryTimesWhenSendFailed,
final SendMessageContext context,
final DefaultMQProducerImpl producer
) throws RemotingException, MQBrokerException, InterruptedException {
// 发送的开始时间
long beginStartTime = System.currentTimeMillis();
RemotingCommand request = null;
// 获得消息类型MSG_TYPE
String msgType = msg.getProperty(MessageConst.PROPERTY_MESSAGE_TYPE);
// reply是否需要回复,发送request消息,RPC调用
boolean isReply = msgType != null && msgType.equals(MixAll.REPLY_MESSAGE_FLAG);
if (isReply) {
if (sendSmartMsg) {
SendMessageRequestHeaderV2 requestHeaderV2 = SendMessageRequestHeaderV2.createSendMessageRequestHeaderV2(requestHeader);
request = RemotingCommand.createRequestCommand(RequestCode.SEND_REPLY_MESSAGE_V2, requestHeaderV2);
} else {
request = RemotingCommand.createRequestCommand(RequestCode.SEND_REPLY_MESSAGE, requestHeader);
}
} else {
// 是否是批量消息
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 {
// 【1】否则的话直接创建,请求码是请求,创建RequestCommand
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);
// 返回null,后面会进行回调
return null;
// 同步的方式
case SYNC:
// 已经花费的时间
long costTimeSync = System.currentTimeMillis() - beginStartTime;
// 是否已经超时了
if (timeoutMillis < costTimeSync) {
throw new RemotingTooMuchRequestException("sendMessage call timeout");
}
// 【2】 同步的方式发送消息,request为Recommand
return this.sendMessageSync(addr, brokerName, msg, timeoutMillis - costTimeSync, request);
default:
assert false;
break;
}
return null;
}
主要逻辑:
- 同步发送消息
- 处理返回结果,因为是同步发送,所以会等待发送结果回来。
private SendResult sendMessageSync(
final String addr, // broker的地址
final String brokerName, // broker的名称
final Message msg, // 消息
final long timeoutMillis, // 超时参数
final RemotingCommand request // 发送内容
) throws RemotingException, MQBrokerException, InterruptedException {
// 【1】发起同步调用
RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis);
assert response != null;
// 【2】处理返回的结果
return this.processSendResponse(brokerName, msg, response, addr);
}
4 网络层发送消息
4.1 同步发送
主要逻辑:
- 获取或者创建与Broker之间的网络通道
Channel
- 同步发送消息
// 发起远程调用 (同步的方式)
@Override
public RemotingCommand invokeSync(String addr, final RemotingCommand request, long timeoutMillis)
throws InterruptedException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException {
long beginStartTime = System.currentTimeMillis();
// 【1】根据ip地址获取或者创建通道
// 发起调用之前是先获得对应的Channel
final Channel channel = this.getAndCreateChannel(addr);
// channel有效
if (channel != null && channel.isActive()) {
try {
//【2】 发起RPC前处理,提供的扩展点
doBeforeRpcHooks(addr, request);
// 花费了多少时间
long costTime = System.currentTimeMillis() - beginStartTime;
if (timeoutMillis < costTime) {
throw new RemotingTimeoutException("invokeSync call timeout");
}
// 【3】发起远程调用,这里会等到返回结果
// 获得对应的Channel后就传递RemotingCommand和Channel进行发送消息
RemotingCommand response = this.invokeSyncImpl(channel, request, timeoutMillis - costTime);
// 远程调用后处理
doAfterRpcHooks(RemotingHelper.parseChannelRemoteAddr(channel), request, response);
return response;
} catch (RemotingSendRequestException e) {
log.warn("invokeSync: send request exception, so close the channel[{}]", addr);
this.closeChannel(addr, channel);
throw e;
// 处理超时异常
} catch (RemotingTimeoutException e) {
if (nettyClientConfig.isClientCloseSocketIfTimeout()) {
this.closeChannel(addr, channel);
log.warn("invokeSync: close socket because of timeout, {}ms, {}", timeoutMillis, addr);
}
log.warn("invokeSync: wait response timeout exception, the channel[{}]", addr);
throw e;
}
} else {
this.closeChannel(addr, channel);
throw new RemotingConnectException(addr);
}
}
同步通信的真正实现
- 创建未来响应结果ResponseFuture
- Channel发送消息进行网络通信
- 调用ResponseFuture的
waitResponse()
方法等待发送结果
public RemotingCommand invokeSyncImpl(final Channel channel, final RemotingCommand request,
final long timeoutMillis)
throws InterruptedException, RemotingSendRequestException, RemotingTimeoutException {
// 获取当前请求的唯一ID
final int opaque = request.getOpaque();
try {
// 【1】创建一个ResponseFuture对象
final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis, null, null);
// 【2】放进缓存表中
this.responseTable.put(opaque, responseFuture);
// 拟调用的地址
final SocketAddress addr = channel.remoteAddress();
// 调用channel进行写发送消息,注册了一个监听器Netty的机制
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);
}
// 发送失败
responseTable.remove(opaque);
responseFuture.setCause(f.cause());
responseFuture.putResponse(null);
log.warn("send a request command to channel <" + addr + "> failed.");
}
});
// 【3】等待发送结果,这是同步发送的关键点,在发送消息后悔在这里等待一段时间,直到有结果返回或者超时间了就退出
// 发送完成后仍然继续等待
RemotingCommand responseCommand = responseFuture.waitResponse(timeoutMillis);
// 如果获取到的响应命令为空
if (null == responseCommand) {
// 如果发送状态为OK,那就是超时了
if (responseFuture.isSendRequestOK()) {
throw new RemotingTimeoutException(RemotingHelper.parseSocketAddressAddr(addr), timeoutMillis,
responseFuture.getCause());
} else {
// 否则就是发送异常,上面设置了不OK
throw new RemotingSendRequestException(RemotingHelper.parseSocketAddressAddr(addr), responseFuture.getCause());
}
}
// 返回结果
return responseCommand;
} finally {
// 最终都会从响应表中移除当前的Future
this.responseTable.remove(opaque);
}
}
5 处理发送结果
处理发送结果就是发过来,先到达网络层,经网络层处理之后,传递给传输层,传输层处理之后传递给应用层。
5.1 网络层处理发送结果
在3.1中同步发送消息后,当前线程会阻塞等待结果。
当Broker接收并处理消息后,会返回生产者客户端一条响应,此条响应会被生产者端的NettyClientHandler
(Netty通信的逻辑)进行处理。
protected void channelRead0(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception {
// 调用处理RemotingCommand形式的消息
processMessageReceived(ctx, msg);
}
根据Command的类型调用不同的处理方法
public void processMessageReceived(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception {
final RemotingCommand cmd = msg;
if (cmd != null) {
// 根据消息的类型,调用不同的函数来进行处理
// getType()是根据RemotingCommand中RPC_TYPE的值来决定是Request还是Response
switch (cmd.getType()) {
// 处理请求命令,客服端向服务端发送请求,将会经过此函数来进行下一步的处理
case REQUEST_COMMAND:
// 处理请求型的Command
processRequestCommand(ctx, cmd);
break;
// 处理的是请求,是消费者主动去拉的,然后有消息读到,所以返回来一个Response进行处理
case RESPONSE_COMMAND:
processResponseCommand(ctx, cmd);
break;
default:
break;
}
}
}
处理响应结果
public void processResponseCommand(ChannelHandlerContext ctx, RemotingCommand cmd) {
// 得到独一无二的不透明码,请求标识
final int opaque = cmd.getOpaque();
// 从responseTable里面拿到之前存储的ResponseFuture
final ResponseFuture responseFuture = responseTable.get(opaque);
if (responseFuture != null) {
// 设置返回的内容
responseFuture.setResponseCommand(cmd);
// 从缓存表中移除
responseTable.remove(opaque);
// 如果responseFuture里面有回调函数
if (responseFuture.getInvokeCallback() != null) {
// 那么就执行去执行它里面的回调
executeInvokeCallback(responseFuture);
} else {
// 设置响应,这个方法里面会通知等待结果的线程已经有返回结果了(使用的是CountLatch)
responseFuture.putResponse(cmd);
// 释放 TODO 作用!
responseFuture.release();
}
} else {
log.warn("receive response, but not matched any request, " + RemotingHelper.parseChannelRemoteAddr(ctx.channel()));
log.warn(cmd.toString());
}
}
5.2 传输层处理响应结果
MQClientAPIImpl
的processSendResponse
方法。
private SendResult processSendResponse(
final String brokerName, // broker
final Message msg, // 消息
final RemotingCommand response, // 响应结果
final String addr // 地址
) throws MQBrokerException, RemotingCommandException {
SendStatus sendStatus;
// 转换具体的返回码
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: {
// 除了前面几种响应码,都抛出异常
throw new MQBrokerException(response.getCode(), response.getRemark(), addr);
}
}
// 从RemotingCommand中解析出消息头协议
SendMessageResponseHeader responseHeader =
(SendMessageResponseHeader) response.decodeCommandCustomHeader(SendMessageResponseHeader.class);
//If namespace not null , reset Topic without namespace.
// 主题
String topic = msg.getTopic();
// 去除主题名之中的nameSpace
if (StringUtils.isNotEmpty(this.clientConfig.getNamespace())) {
topic = NamespaceUtil.withoutNamespace(topic, this.clientConfig.getNamespace());
}
// 创建消息队列对象
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;
}