1 生产端发送消息
2 发送消息流程
- 一个是可以批量发送的流程
- 一个是直接发送,
以直接发送的方法为节点,只分析批量发送的流程。之后再分析直接发送的流程
@Override
public SendResult send(
Message msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
msg.setTopic(withNamespace(msg.getTopic()));
if (this.getAutoBatch() && !(msg instanceof MessageBatch)) {
return sendByAccumulator(msg, null, null); // TODO 批量发送
} else {
return sendDirect(msg, null, null); // TODO 直接发送
}
}
DefaultMQProducer#sendByAccumulator
- 如果不能批量发送则走直接发送的方法 sendDirect
- 如果走的是批量发送,则将消息发送到生产者积累器中 produceAccumulator
org.apache.rocketmq.client.producer.ProduceAccumulator#send(org.apache.rocketmq.common.message.Message, org.apache.rocketmq.common.message.MessageQueue, org.apache.rocketmq.client.producer.SendCallback, org.apache.rocketmq.client.producer.DefaultMQProducer)
public SendResult sendByAccumulator(Message msg, MessageQueue mq,
SendCallback sendCallback) throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
// check whether it can batch
if (!canBatch(msg)) { //TODO 判断是否需要进行批量操作发送
return sendDirect(msg, mq, sendCallback); //TODO 直接发送
} else {
Validators.checkMessage(msg, this);
MessageClientIDSetter.setUniqID(msg); // TODO 设置唯一的msg的ID
if (sendCallback == null) {
return this.produceAccumulator.send(msg, mq, this);
} else {
this.produceAccumulator.send(msg, mq, sendCallback, this); // TODO 将消息发送到生产者积累器 produceAccumulator 中
return null;
}
}
}
ProduceAccumulator#send
- 将msg添加到消息积累器中MessageAccumulation
- 如果添加成果成功,直接退出死循环
- 如果添加批量不成功,说明消息积累器已经满足发送的条件需要发送消息,清空批量缓存
- 添加成功之后跳出while (true)
void send(Message msg, MessageQueue mq,
SendCallback sendCallback,
DefaultMQProducer defaultMQProducer) throws InterruptedException, RemotingException, MQClientException {
AggregateKey partitionKey = new AggregateKey(msg, mq);
while (true) {
MessageAccumulation batch = getOrCreateAsyncSendBatch(partitionKey, defaultMQProducer);
if (!batch.add(msg, sendCallback)) { // TODO 将消息发送添加到 MessageAccumulation 消息积累器中
asyncSendBatchs.remove(partitionKey, batch);
} else {
return;
}
}
}
MessageAccumulation#add (将消息添加到消息积累器中)
- 将消息缓存到LinkedList
- 刷新消息积累器中的消息实际容量
- 判断是否准备好发送,准备好发送,进行直接发送
public boolean add(Message msg,
SendCallback sendCallback) throws InterruptedException, RemotingException, MQClientException {
synchronized (this.closed) {
if (this.closed.get()) {
return false;
}
this.count++;
this.messages.add(msg); // TODO 存放List缓存中
this.sendCallbacks.add(sendCallback);
messagesSize.getAndAdd(msg.getBody().length); //TODO 记录现有的积累器中消息的存储长度
}
if (readyToSend()) { // TODO 准备好 去发送
this.send(sendCallback); // TODO 真正发送消息的位置
}
return true;
}
消息的实际长度 大于 持有消息长度的值 或者消息的消息积累器的的实际存在时间大于实际持有时间,则进行清理消息积累器中的消息 发送消息
3 直接发送消息的源码分析
3.1 入口
DefaultMQProducerImpl#sendDefaultImpl
-
获取主题:获取发布的主题信息。this.tryToFindTopicPublishInfo(msg.getTopic())
- 选择发送的队列 selectOneMessageQueue
-
重试逻辑:重试次数 异步的重试次数是1
long costTime = beginTimestampPrev - beginTimestampFirst;
-
投递超时:投递消息的超时时间 则退出重试循环,不在进行重试投递消息(包括首次发送消息)
-
发送消息 (后续发送消息的流程)
this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout - costTime);
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 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;
// TODO 2 重试次数 异步的重试次数是1
int timesTotal = communicationMode == CommunicationMode.SYNC ? 1 + this.defaultMQProducer.getRetryTimesWhenSendFailed() : 1;
int times = 0;
String[] brokersSent = new String[timesTotal];
boolean resetIndex = false;
for (; times < timesTotal; times++) {
String lastBrokerName = null == mq ? null : mq.getBrokerName();
if (times > 0) {
resetIndex = true;
}
//TODO 3 选出一个需要发送的队列
MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName, resetIndex);
if (mqSelected != null) {
mq = mqSelected;
brokersSent[times] = mq.getBrokerName();
try {
beginTimestampPrev = System.currentTimeMillis();
if (times > 0) {
//Reset topic with namespace during resend.
msg.setTopic(this.defaultMQProducer.withNamespace(msg.getTopic()));
}
long costTime = beginTimestampPrev - beginTimestampFirst; // TODO 投递消息的超时时间 则退出重试循环,不在进行重试投递消息(包括首次发送消息)、
if (timeout < costTime) {
callTimeout = true;
break;
}
// TODO 发送消息
sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout - costTime);
endTimestamp = System.currentTimeMillis();
this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false, true);
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 (MQClientException e) {
......
}
3.2 获取发布的主题路由信息
- 从缓存的主题发布信息中过去指定主题信息
- 如果缓存没有,从nameServer中刷新主题信息到缓存中
- 如果获取的主题信息中路由信息不正常(没有包含路由信息),也需要从NameServer中刷新缓存
- 需要分析从NameServer获取主题路由信息的点:
updateTopicRouteInfoFromNameServer
从NameServe中更新主题路由信息
- 默认主题的分支,需要逻辑判断默认 主题的可读可写队列的数量,可读队列和生产者默认的队列数量中取最小值
- 不走默认主题:直接去NameServe获取主题路由数据
this.mQClientAPIImpl.getTopicRouteInfoFromNameServer
this.mQClientAPIImpl.getTopicRouteInfoFromNameServer方法最终调用啦Netty API 从NameServe获取主题路由信息
这是获取主题的直接通信的逻辑,发送消息的通信逻辑和此处类似。
4 借鉴点
- 对于Netty的应用:RocketMQ对Netty按照自己的领域概念封装成remote远程调用领域
所有对远程通信的地方都是切换成和remoting模块的交互 - 对消息的发送:分为直接发送和批量发送,利用消息积累器对消息进行积累进行发送,按照
消息大小和存储的时间实现对消息的批量发送 - 对发送重试逻辑的实现逻辑可以参考
5 下节内容
- Broker和Producer的建立长链接
- Broker 接受消息投递和消息持久化索引化的逻辑