源码版本号:版本号:4.9.4
生产者发送消息时,需要拿到topic的所有队列,从队列列表里面选择一个队列进行发送。
消息队列负载和重新分布时需要拿到topic的所有队列,然后计算出自己应该消费哪些队列。
所以生产者和消费者需要知道最新的topic有哪些队列。
对于生产者,发送消息时,会先从DefaultMQProducerImpl
中的topicPublishInfoTable
属性中获取,
如果topicPublishInfoTable
属性中不存在,则会调用
MQClientInstance.updateTopicRouteInfoFromNameServer(java.lang.String)
从NameServer
获取最新的topic信息。
对于消费者,启动后就会调用DefaultMQPushConsumerImpl#updateTopicSubscribeInfoWhenSubscriptionChanged
方法,
遍历自己订阅的topic信息,
然后调用MQClientInstance#updateTopicRouteInfoFromNameServer(java.lang.String)
从NameServer
获取最新的topic信息。
生产者和消费者都会主动触发updateTopicRouteInfoFromNameServer
来获取最新的topic信息,除了主动触发,还会有定时任务触发。
MQClientInstance
启动的定时任务
public class MQClientInstance {
// 256行
private void startScheduledTask() {
// 271行 每隔30s执行一次
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
MQClientInstance.this.updateTopicRouteInfoFromNameServer();
} catch (Exception e) {
log.error("ScheduledTask updateTopicRouteInfoFromNameServer exception", e);
}
}
}, 10, this.clientConfig.getPollNameServerInterval(), TimeUnit.MILLISECONDS);
}
}
updateTopicRouteInfoFromNameServer
会遍历所有消费者订阅的topic和
所有生产者DefaultMQProducerImpl
中的topicPublishInfoTable
保存的topic,
调用MQClientInstance#updateTopicRouteInfoFromNameServer(java.lang.String)
从NameServer
获取最新的topic信息。
public class MQClientInstance {
private final static long LOCK_TIMEOUT_MILLIS = 3000;
private final Lock lockNamesrv = new ReentrantLock();
/**
* 用来保存topic的路由信息, 从nameserver获取来的信息直接保存到这里
* TopicRouteData包含
* QueueData列表(brokerName、readQueueNums、writeQueueNums)
* BrokerData列表(cluster、brokerName、brokerAddrs(key=brokerId, value=brokerId的地址))
*/
private final ConcurrentMap<String/* Topic */, TopicRouteData> topicRouteTable = new ConcurrentHashMap<String, TopicRouteData>();
/**
* 保存brokerName、brokerId、brokerAddress
*/
private final ConcurrentMap<String/* Broker Name */, HashMap<Long/* brokerId */, String/* address */>> brokerAddrTable =
new ConcurrentHashMap<String, HashMap<Long, String>>();
/**
* 版本号:4.9.4
* 找到509行
* MQClientInstance启动的时候会生成一个定时任务
* 每隔30s执行一次(获取所有生产者和消费者里面的topic集合, 然后分别调用该方法)
*/
public boolean updateTopicRouteInfoFromNameServer(final String topic) {
return updateTopicRouteInfoFromNameServer(topic, false, null);
}
/**
* 找到606行
* @param isDefault
* 如果为false, 如果这个topic还没有创建, 那么nameserver是查询不到的
* 如果为true, 则获取默认的topic:TBW102(TopicValidator.AUTO_CREATE_TOPIC_KEY_TOPIC)
*/
public boolean updateTopicRouteInfoFromNameServer(final String topic, boolean isDefault,
DefaultMQProducer defaultMQProducer) {
try {
// 加锁
if (this.lockNamesrv.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
try {
TopicRouteData topicRouteData;
if (isDefault && defaultMQProducer != null) {
/**
* 查询默认的topic信息, 可以发现并没有传该方法的topic字段
* 查询默认的topic主要是为了拿到默认topic的读写队列个数, 这样生产者才能选择队列然后进行发送消息
* 在broker保存消息的时候, 如果topic不存在, 如果允许自动创建topic, 此时才会在broker生成topic信息
*/
topicRouteData = this.mQClientAPIImpl.getDefaultTopicRouteInfoFromNameServer(defaultMQProducer.getCreateTopicKey(),
clientConfig.getMqClientApiTimeout());
if (topicRouteData != null) {
for (QueueData data : topicRouteData.getQueueDatas()) {
int queueNums = Math.min(defaultMQProducer.getDefaultTopicQueueNums(), data.getReadQueueNums());
data.setReadQueueNums(queueNums);
data.setWriteQueueNums(queueNums);
}
}
} else {
// 如果查询不到对应的topic信息
topicRouteData = this.mQClientAPIImpl.getTopicRouteInfoFromNameServer(topic, clientConfig.getMqClientApiTimeout());
}
if (topicRouteData != null) {
/**
* 省略
* 1.更新brokerAddrTable
* 2.遍历producerTable 更新每个DefaultMQProducerImpl中的topicPublishInfoTable
* 这样生产者就知道每个topic都有哪些队列
* 3.遍历consumerTable 更新每个消费者中的topicSubscribeInfoTable(在RebalanceImpl中)
* 这样消费者就知道每个topic都有哪些队列, 然后根据规则计算出自己需要拉取哪些队列的消息
* 4.更新topicRouteTable
*/
return true;
}
} catch (Exception e) {
// 省略掉异常处理
} finally {
// 解锁
this.lockNamesrv.unlock();
}
}
} catch (InterruptedException e) {
log.warn("updateTopicRouteInfoFromNameServer Exception", e);
}
return false;
}
}
为什么NameServer会有所有的topic信息,topic信息不是保存在Broker吗?
Broker启动后,会有定时任务每隔30s向NameServer发送心跳信息,这些心跳信息就包括所有的topic信息。