Rocketmq中的NameServer主要负责两个工作,broker管理和路由管理。这篇文章主要是分析NameServer如何完成这两个工作的。
一、broker管理
broker会定时上报broker的基本信息以及主题信息给NameServer,NameServer会将这些信息存储到RouteInfoManager中
1. broker上报信息
我们看看broker启动时关于上报信息的代码
//位于BrokerController.start
this.registerBrokerAll(true, false, true);
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
BrokerController.this.registerBrokerAll(true, false, brokerConfig.isForceRegister());
} catch (Throwable e) {
log.error("registerBrokerAll Exception", e);
}
}
}, 1000 * 10, Math.max(10000, Math.min(brokerConfig.getRegisterNameServerPeriod(), 60000)), TimeUnit.MILLISECONDS);
可以看出,broker启动的时候会立刻向NameServer注册,然后会启动定时任务(默认30s)向NameServer同步自己的注册信息。
接下来关注具体的register操作。第一步,组装上报的信息,包括broker地址、brokerId、brokerName、clusterName、主题信息等
//位于BrokerOuterAPI.registerBrokerAll
final RegisterBrokerRequestHeader requestHeader = new RegisterBrokerRequestHeader();
requestHeader.setBrokerAddr(brokerAddr);
requestHeader.setBrokerId(brokerId);
requestHeader.setBrokerName(brokerName);
requestHeader.setClusterName(clusterName);
requestHeader.setHaServerAddr(haServerAddr);
requestHeader.setCompressed(compressed);
RegisterBrokerBody requestBody = new RegisterBrokerBody();
requestBody.setTopicConfigSerializeWrapper(topicConfigWrapper);
requestBody.setFilterServerList(filterServerList);
第二步,多线程发送注册信息给所有NameServer,并使用CountDownLatch等待所有线程完成注册任务。
//位于BrokerOuterAPI.registerBrokerAll
final CountDownLatch countDownLatch = new CountDownLatch(nameServerAddressList.size());
for (final String namesrvAddr : nameServerAddressList) {
brokerOuterExecutor.execute(new Runnable() {
@Override
public void run() {
try {
RegisterBrokerResult result = registerBroker(namesrvAddr,oneway, timeoutMills,requestHeader,body);
if (result != null) {
registerBrokerResultList.add(result);
}
log.info("register broker to name server {} OK", namesrvAddr);
} catch (Exception e) {
log.warn("registerBroker Exception, {}", namesrvAddr, e);
} finally {
countDownLatch.countDown();
}
}
});
}
try {
countDownLatch.await(timeoutMills, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {}
2. NameServer存储broker的信息
NameServer接收到broker的register请求后,会从中取出broker信息存放在RouteInfoManager中
//位于DefaultRequestProcessor.registerBroker
final RegisterBrokerRequestHeader requestHeader = (RegisterBrokerRequestHeader)request.decodeCommandCustomHeader(RegisterBrokerRequestHeader.class);
TopicConfigSerializeWrapper topicConfigWrapper;
if (request.getBody() != null) {
topicConfigWrapper = TopicConfigSerializeWrapper.decode(request.getBody(), TopicConfigSerializeWrapper.class);
} else {
topicConfigWrapper = new TopicConfigSerializeWrapper();
topicConfigWrapper.getDataVersion().setCounter(new AtomicLong(0));
topicConfigWrapper.getDataVersion().setTimestamp(0);
}
RegisterBrokerResult result = this.namesrvController.getRouteInfoManager().registerBroker(
requestHeader.getClusterName(),
requestHeader.getBrokerAddr(),
requestHeader.getBrokerName(),
requestHeader.getBrokerId(),
requestHeader.getHaServerAddr(),
topicConfigWrapper,
null,
ctx.channel()
);
总结一下,broker管理的流程大概就是
(1)broker启动向每一台NameServer上报信息(包括broker地址、brokerId、brokerName、clusterName、主题信息等)
(2)broker定时向每一台NameServer同步信息
(3)nameServer接收broker信息后存储在RouteInfoManager中
可以知道,NameServer之间是相互独立的管理着broker的信息。
二、路由管理
客户端通过NameServer获取路由信息,然后直接与broker进行通讯。
1. 客户端请求路由信息
那什么时候会获取路由信息呢?客户端会在启动时设置一些定时任务,包括(1)定时(默认30s)获取RouteInfo(2)定时(默认30s)根据RouteInfo来剔除broker列表中掉线的实例。
//MQClientInstane.startScheduledTask
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);
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
MQClientInstance.this.cleanOfflineBroker();
MQClientInstance.this.sendHeartbeatToAllBrokerWithLock();
} catch (Exception e) {
log.error("ScheduledTask sendHeartbeatToAllBroker exception", e);
}
}
}, 1000, this.clientConfig.getHeartbeatBrokerInterval(),TimeUnit.MILLISECONDS);
即使没有的及时更新主题信息,当客户端发现找不到目标主题的信息,也会主动获取并更新到本地。
2. 客户端与NameServer建立连接
客户端会与其中一台NameServer保持通讯,namesrvAddrChoosed标识着当前通讯的NameServer,长连接会缓存在channelTables中方便重用。如果与该台NameServer连接出现异常,会按序寻找下一个可用的。
//NettyRemotingClient.getAndCreateNameserverChannel
addr = this.namesrvAddrChoosed.get();
if (addr != null) {
ChannelWrapper cw = this.channelTables.get(addr);
if (cw != null && cw.isOK()) {
return cw.getChannel();
}
}
if (addrList != null && !addrList.isEmpty()) {
for (int i = 0; i < addrList.size(); i++) {
int index = this.namesrvIndex.incrementAndGet();
index = Math.abs(index);
index = index % addrList.size();
String newAddr = addrList.get(index);
this.namesrvAddrChoosed.set(newAddr);
Channel channelNew = this.createChannel(newAddr);
if (channelNew != null) {
return channelNew;
}
}
}
3. NameServer响应路由请求
当NameServer收到客户端发送过来的GET_ROUTEINTO_BY_TOPIC消息后,会从RouteInfoManager中取出主题信息返回给客户端,客户端就可以直接和broker进行通讯了,至于和哪一台broker,具体由客户端来决定(写的话只允许是master,master宕掉则不可写;读的话会选取broker返回来的推荐broker值,该broker宕掉则换下一台)。
总结一下,路由的过程是
(1)客户端定时向NameServer请求客户端所关注的topic的路由信息
(2)在没有可用的路由信息时,客户端主动请求NameServer
(3)NameServer会从RouteInfoManager取出路由信息返回给客户端
(4)客户端会将路由信息缓存起来
(5)客户端具体与哪一个broker通讯,会参考broker的建议或者自行决定,NameServer只负责返回路由信息。