概述
消息发送到RocketMQ服务器涉及到下面几个概念
- 生产者
生产者是消息生成的实体
- 主题(Topic)
Topic是消息传输和存储的顶层容器,通常用于标识同意类型的业务逻辑消息,它是由多个队列组成,消息的存储和水平扩展是通过Topic内队列来实现的。
- 队列(MessageQueue)
队列是消息存储和传输的实际单元容器,它相当于Kakfa中的分区(partition)。MessageQueue代码如下所示,它主要包含topic,brokername,queueId三个属性。
public class MessageQueue implements Comparable<MessageQueue>, Serializable {
// topic
private String topic;
// broker名称
private String brokerName;
// queueId
private int queueId;
}
Producer中3中发送消息的方式
我们通过Producer发送消息可以指定需要发送的队列,或者让Producer帮我们选择要发送的队列,从MessageQueue选择的角度来看,RocketMQ client的Producer提供了3种不同类型的消息推送方法
- 自动选择MessageQueue
在发送消息时只传入Message,Producer会自动选择MessageQueue
SendResult send(final Message msg);
- 将消息推送到指定的MessageQueue
发送消息时手动指定MessageQueue
SendResult send(final Message msg, final MessageQueue mq);
- 通过MessageQueueSelector选择MessageQueue
根据用户定义的MessageQueueSelector选择要推送的MessageQueue
SendResult send(final Message msg, final MessageQueueSelector selector, final Object arg);
MessageQueueSelector是一个接口,我们可以根据需求自定义一个MessageQueue选择算法,将消息推送到指定的Messagequeue中。
public interface MessageQueueSelector {
MessageQueue select(final List<MessageQueue> mqs/*当前Topic对应的MessageQueue*/, final Message msg, final Object arg);
}
官方也给我们提供了3个默认的MessageQueueSelector实现类
- 通过Hash选择MessageQueue
它通过参数arg的hash与mqs.size()取模,来选择要存储的队列
public class SelectMessageQueueByHash implements MessageQueueSelector {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
int value = arg.hashCode() % mqs.size();
if (value < 0) {
value = Math.abs(value);
}
return mqs.get(value);
}
}
- 随机选择MessageQueue
随机产生一个0 ≤ value ≤ mqs.size()的整数,来选择MessageQueue
public class SelectMessageQueueByRandom implements MessageQueueSelector {
private Random random = new Random(System.currentTimeMillis());
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
int value = random.nextInt(mqs.size());
return mqs.get(value);
}
}
- 根据机房信息选择MessageQueue
可以看到SelectMessageQueueByMachineRoom返回了一个null值,它需要我们根据场景实现,不能直接使用。
public class SelectMessageQueueByMachineRoom implements MessageQueueSelector {
private Set<String> consumeridcs;
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
return null;
}
public Set<String> getConsumeridcs() {
return consumeridcs;
}
public void setConsumeridcs(Set<String> consumeridcs) {
this.consumeridcs = consumeridcs;
}
}
如果RocketMQ默认的MessageQueueSelector实现不能满足业务的需求,我们还可以根据自己的需要实现MessageQueueSelector。
Producer路由选择源码分析
DefaultMQProducer的send方法底层都是通过调用DefaultMQProducerImpl的方法实现的,DefaultMQProducerImpl是Producer方法的实现类。下面3个时序图展示了消息推送的过程,由于本文是分析Producer是如何选择MessageQueue的,因此时序图中的调用方法做了精简,仅保留路由选择相关的方法。
自动选择MessageQueue源码分析
如下图所示,如果用户调用send方法仅传入Message时,整个消息发送过程步骤如下
- DefaultMQProducer调用DefaultMQProducerImpl的send(Message)方法推送消息
- DefaultMQProducerImpl调用tryToFindTopicPublishInfo获得topic相关信息
- DefaultMQProducerImpl 调用selectOneMessageQueue获得MessageQueue
- DefaultMQProducerImpl调用sendKernelImpl推送消息,并返回推送结果(SendResult)
消息发送关键代码如下所示
private SendResult sendDefaultImpl(
Message msg,
final CommunicationMode communicationMode /*同步,异步,oneWay模式*/,
final SendCallback sendCallback,
final long timeout
) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
// 省略部分代码
// 根据topic获取topic路由信息
TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());
String lastBrokerName = null == mq ? null : mq.getBrokerName();
// 省略部分代码
// 选择MessageQueue
MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);
// 省略部分代码
// 推送消息消息
sendResult = this.sendKernelImpl(msg,mq,communicationMode,sendCallback,topicPublishInfo,timeout-costTime);
// 省略部分代码
return sendResult;
// 省略部分代码
}
我们来看下是如何根据topic获取Topic路由信息的(TopicPublishInfo),DefaultMQProducerImpl#tryToFindTopicPublishInfo
源码如下
// topic和topic路由信息类,key是topic,
private final ConcurrentMap<String/* topic */, TopicPublishInfo> topicPublishInfoTable =
new ConcurrentHashMap<>();
private TopicPublishInfo tryToFindTopicPublishInfo(final String topic) {
TopicPublishInfo topicPublishInfo = this.topicPublishInfoTable.get(topic);
// 如果topic没有MessageQueue信息
if (null == topicPublishInfo || !topicPublishInfo.ok()) {
this.topicPublishInfoTable.putIfAbsent(topic, new TopicPublishInfo());
// 从namesrv更新topic信息
this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic);
topicPublishInfo = this.topicPublishInfoTable.get(topic);
}
if (topicPublishInfo.isHaveTopicRouterInfo() || topicPublishInfo.ok()) {
return topicPublishInfo;
} else {
this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic, true, this.defaultMQProducer);
topicPublishInfo = this.topicPublishInfoTable.get(topic);
return topicPublishInfo;
}
}
topic路由信息是缓存在DefaultMQProducerImpl中的Map中,获取topic路由信息首先会根据topic查询缓存Map中的路由信息,如果缓存中的路由信息不存在,则会去namesrv中查询topic的路由信息,并更新到本地缓存中。
查询到路由信息后,会调用selectOneMessageQueue方法选择MessageQueue。
public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName) {
// 是否打开发送延时故障启动机制 默认是关闭的
if (this.sendLatencyFaultEnable) {
try {
// 获取MessageQueue选择索引,并+1
int index = tpInfo.getSendWhichQueue().incrementAndGet();
for (int i = 0; i < tpInfo.getMessageQueueList().size(); i++) {
// index与messageQueueSize取余,如果可用,则返回,否则选择下一个MessageQueue
int pos = index++ % tpInfo.getMessageQueueList().size();
MessageQueue mq = tpInfo.getMessageQueueList().get(pos);
if (latencyFaultTolerance.isAvailable(mq.getBrokerName()))
return mq;
}
// 随机选择一个broker
final String notBestBroker = latencyFaultTolerance.pickOneAtLeast();
// 轮询选择一个写队列
int writeQueueNums = tpInfo.getWriteQueueIdByBroker(notBestBroker);
if (writeQueueNums > 0) {
final MessageQueue mq = tpInfo.selectOneMessageQueue();
if (notBestBroker != null) {
mq.setBrokerName(notBestBroker);
mq.setQueueId(tpInfo.getSendWhichQueue().incrementAndGet() % writeQueueNums);
}
return mq;
} else {
latencyFaultTolerance.remove(notBestBroker);
}
} catch (Exception e) {
log.error("Error occurred when selecting message queue", e);
}
return tpInfo.selectOneMessageQueue();
}
// 轮询选择MessageQueue
return tpInfo.selectOneMessageQueue(lastBrokerName);
}
选择MessageQueue流程如下
指定MessageQueue
发送消息时指定MessageQueue,消息推送过程就不需要再选择MessageQueue。
通过MessageQueueSelector选择MessageQueue
发送消息时传入MessageQueueSelector,消息推送过程如下
- DefaultMQProducerImpl调用tryToFindTopicPublishInfo获得topic相关信息
- 将
List<MessageQueue>
传入selector,选择MessageQueue - DefaultMQProducerImpl调用sendKernelImpl推送消息,并返回推送结果(SendResult)