目录
主要功能
NameServer是一个简单的 Topic 路由注册中心,支持 Topic、Broker 的动态注册与发现。
主要包括两个功能:
- Broker管理,NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制,检查Broker是否还存活;
- 路由信息管理,每个NameServer将保存关于 Broker
集群的整个路由信息和用于客户端查询的队列信息。Producer和Consumer通过NameServer就可以知道整个Broker集群的路由信息,从而进行消息的投递和消费。
NameServer通常会有多个实例部署,各实例间相互不进行信息通讯。Broker是向每一台NameServer注册自己的路由信息,所以每一个NameServer实例上面都保存一份完整的路由信息。当某个NameServer因某种原因下线了,客户端仍然可以向其它NameServer获取路由信息。
源码解析
目录结构及各类作用
- NamesrvStartup:namesrv启动类,加载配置文件信息并初始化NamesrvController
- NamesrvController:NameServer核心控制器,里边包含初始化方法和配置信息
- KVConfigManager:包含NamesrvController和一个HashMap,主要是要来存储参数信息
- KVConfigSerializeWrapper:配合KVConfigManager使用
- ClusterTestRequestProcessor:功能类似DefaultRequestProcessor,当参数clusterTest为true是走这个逻辑,否则走DefaultRequestProcessor
- DefaultRequestProcessor: 请求处理器,比如获取topic信息,注册broker等
- BrokerHousekeepingService:实现ChannelEventListener接口,监听channel事件并作出相应的处理。比如某个broker关闭了,会触发channel事件,把broker注册信息从nameserver删除
- BrokerLiveInfo:broker信息模型,存储一些broker信息,此类主要给RouteInfoManager使用
- RouteInfoManager:主要是broker和topic的操作逻辑,比如:删除topic,注册broker
其中核心的类为: NamesrvStartup,DefaultRequestProcessor,RouteInfoManager。下边的源码解析也是围绕着这几个类进行。
NamesrvStartup
public static void main(String[] args) {
main0(args);
}
public static NamesrvController main0(String[] args) {
try {
NamesrvController controller = createNamesrvController(args);
start(controller);
String tip = "The Name Server boot success. serializeType=" + RemotingCommand.getSerializeTypeConfigInThisServer();
log.info(tip);
System.out.printf("%s%n", tip);
return controller;
} catch (Throwable e) {
e.printStackTrace();
System.exit(-1);
}
return null;
}
此代码是Namesrv启动类,此文中主要代码是start(controller),我们继续向下看。
public static NamesrvController start(final NamesrvController controller) throws Exception {
if (null == controller) {
throw new IllegalArgumentException("NamesrvController is null");
}
//初始化NamesrvController
boolean initResult = controller.initialize();
if (!initResult) {
controller.shutdown();
System.exit(-3);
}
//对当前运行程序注册钩子函数,:关闭该进程时,关闭线程池,释放资源。一种优雅停机的方式就是注册钩子函数。
//该方法用来在jvm中增加一个关闭的钩子。当程序正常退出,系统调用 System.exit方法或虚拟机被关闭时才会执行添加的shutdownHook线程。
//其中shutdownHook是一个已初始化但并不有启动的线程,当jvm关闭的时候,会执行系统中已经设置的所有通过方法addShutdownHook添加的钩子,
//当系统执行完这些钩子后,jvm才会关闭。所以可通过这些钩子在jvm关闭的时候进行内存清理、资源回收等工作。
Runtime.getRuntime().addShutdownHook(new ShutdownHookThread(log, (Callable<Void>) () -> {
controller.shutdown();
return null;
}));
//启动
controller.start();
return controller;
}
此段代码主要功能注释已解释清楚,核心代码就是:controller.initialize(),这个类的主要的主要目的是初始化NamesrvController。
public boolean initialize() {
//加载nameserconfig到configTable,是个hashmap
this.kvConfigManager.load();
//构建个NettyRemotingServer,用作netty网络处理
this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);
//初始化线程池
this.remotingExecutor =
Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), new ThreadFactoryImpl("RemotingExecutorThread_"));
//把NettyRequestProcessor和线程池绑定
this.registerProcessor();
// 定时任务:每10秒扫描一次broker,移除处于不活跃状态的broker,此方法后续会详细学习
this.scheduledExecutorService.scheduleAtFixedRate(NamesrvController.this.routeInfoManager::scanNotActiveBroker, 5, 10, TimeUnit.SECONDS);
// 定时任务:每10分钟打印一次KV配置,也就是namesrv参数信息
this.scheduledExecutorService.scheduleAtFixedRate(NamesrvController.this.kvConfigManager::printAllPeriodically, 1, 10, TimeUnit.MINUTES);
if (TlsSystemConfig.tlsMode != TlsMode.DISABLED) {
// Register a listener to reload SslContext 使用SSL/TLS的通信,就是为了防止信息被第三方窃听、篡改、冒充。
try {
fileWatchService = new FileWatchService(
new String[] {
TlsSystemConfig.tlsServerCertPath,
TlsSystemConfig.tlsServerKeyPath,
TlsSystemConfig.tlsServerTrustCertPath
},
new FileWatchService.Listener() {
boolean certChanged, keyChanged = false;
@Override
public void onChanged(String path) {
if (path.equals(TlsSystemConfig.tlsServerTrustCertPath)) {
log.info("The trust certificate changed, reload the ssl context");
reloadServerSslContext();
}
if (path.equals(TlsSystemConfig.tlsServerCertPath)) {
certChanged = true;
}
if (path.equals(TlsSystemConfig.tlsServerKeyPath)) {
keyChanged = true;
}
if (certChanged && keyChanged) {
log.info("The certificate and private key changed, reload the ssl context");
certChanged = keyChanged = false;
reloadServerSslContext();
}
}
private void reloadServerSslContext() {
((NettyRemotingServer) remotingServer).loadSslContext();
}
});
} catch (Exception e) {
log.warn("FileWatchService created error, can't load the certificate dynamically");
}
}
return true;
}
initialize()类是核心类,这里有整个NamesrvController初始化逻辑。
- 加载了nameserconfig配置信息到configTable中
- 加载了nettyServerConfig配置信息到NettyRemotingServer
- 为NettyRequestProcessor分配线程池
- 创建定时任务:每10秒扫描一次broker,移除处于不活跃状态的broker
- 创建定时任务:每10分钟打印一次KV配置,也就是namesrv参数信息
- 注册一个listener去加载SslContext,使用SSL/TLS的通信,就是为了防止信息被第三方窃听、篡改、冒充。
DefaultRequestProcessor
DefaultRequestProcessor是请求处理类,是处理所有请求的。我们首先来看下这个类的功能。
public class DefaultRequestProcessor extends AsyncNettyRequestProcessor implements NettyRequestProcessor {
这个类实现了接口NettyRequestProcessor(),里边有两个接口
public interface NettyRequestProcessor {
RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request)
throws Exception;
boolean rejectRequest();
}
processRequest()是处理请求,rejectRequest()是拒绝请求。
同时DefaultRequestProcessor也继承了AsyncNettyRequestProcessor
public abstract class AsyncNettyRequestProcessor implements NettyRequestProcessor {
public void asyncProcessRequest(ChannelHandlerContext ctx, RemotingCommand request, RemotingResponseCallback responseCallback) throws Exception {
RemotingCommand response = processRequest(ctx, request);
responseCallback.callback(response);
}
}
这个抽象类里有个异步方法,也就是异步处理请求的逻辑。因为异步逻辑已经写好了,所以DefaultRequestProcessor只要实现NettyRequestProcessor中的两个方法即可。
继续看DefaultRequestProcessor方法,其中rejectRequest()很简单,就是返回false就可以了。
@Override
public boolean rejectRequest() {
return false;
}
接下来继续看processRequest()方法
@Override
public RemotingCommand processRequest(ChannelHandlerContext ctx,
RemotingCommand request) throws RemotingCommandException {
if (ctx != null) {
log.debug("receive request, {} {} {}",
request.getCode(),
RemotingHelper.parseChannelRemoteAddr(ctx.channel()),
request);
}
switch (request.getCode()) {
case RequestCode.PUT_KV_CONFIG:
return this.putKVConfig(ctx, request);
case RequestCode.GET_KV_CONFIG:
return this.getKVConfig(ctx, request);
case RequestCode.DELETE_KV_CONFIG:
return this.deleteKVConfig(ctx, request);
case RequestCode.QUERY_DATA_VERSION:
return queryBrokerTopicConfig(ctx, request);
case RequestCode.REGISTER_BROKER:
Version brokerVersion = MQVersion.value2Version(request.getVersion());
if (brokerVersion.ordinal() >= MQVersion.Version.V3_0_11.ordinal()) {
return this.registerBrokerWithFilterServer(ctx, request);
} else {
return this.registerBroker(ctx, request);
}
case RequestCode.UNREGISTER_BROKER:
return this.unregisterBroker(ctx, request);
case RequestCode.GET_ROUTEINFO_BY_TOPIC:
return this.getRouteInfoByTopic(ctx, request);
case RequestCode.GET_BROKER_CLUSTER_INFO:
return this.getBrokerClusterInfo(ctx, request);
case RequestCode.WIPE_WRITE_PERM_OF_BROKER:
return this.wipeWritePermOfBroker(ctx, request);
case RequestCode.ADD_WRITE_PERM_OF_BROKER:
return this.addWritePermOfBroker(ctx, request);
case RequestCode.GET_ALL_TOPIC_LIST_FROM_NAMESERVER:
return getAllTopicListFromNameserver(ctx, request);
case RequestCode.DELETE_TOPIC_IN_NAMESRV:
return deleteTopicInNamesrv(ctx, request);
case RequestCode.GET_KVLIST_BY_NAMESPACE:
return this.getKVListByNamespace(ctx, request);
case RequestCode.GET_TOPICS_BY_CLUSTER:
return this.getTopicsByCluster(ctx, request);
case RequestCode.GET_SYSTEM_TOPIC_LIST_FROM_NS:
return this.getSystemTopicListFromNs(ctx, request);
case RequestCode.GET_UNIT_TOPIC_LIST:
return this.getUnitTopicList(ctx, request);
case RequestCode.GET_HAS_UNIT_SUB_TOPIC_LIST:
return this.getHasUnitSubTopicList(ctx, request);
case RequestCode.GET_HAS_UNIT_SUB_UNUNIT_TOPIC_LIST:
return this.getHasUnitSubUnUnitTopicList(ctx, request);
case RequestCode.UPDATE_NAMESRV_CONFIG:
return this.updateConfig(ctx, request);
case RequestCode.GET_NAMESRV_CONFIG:
return this.getConfig(ctx, request);
default:
break;
}
return null;
}
请求处理方法,根据不同请求类型走相应的处理逻辑。因为逻辑过多,挑选几个比较重要的方法仔细学习。
registerBrokerWithFilterServer
这个方法是注册broker到nameServer,版本V3_0_11及以上的使用此方法,主要看这个方法。请求REGISTER_BROKER = 103的请求走此方法。接下来我们看请求入口。
入口实在broker模块的BrokerStartup()启动方法中,因为这次主要讲的是nameServer,BrokerStartup()就大概讲一下。BrokerStartup()方法后边会详细的学习,这里只是简单带过一下。
入口代码:
public static BrokerController start(BrokerController controller) {
try {
controller.start();
String tip = "The broker[" + controller.getBrokerConfig().getBrokerName() + ", "
+ controller.getBrokerAddr() + "] boot success. serializeType=" + RemotingCommand.getSerializeTypeConfigInThisServer();
if (null != controller.getBrokerConfig().getNamesrvAddr()) {
tip += " and name server is " + controller.getBrokerConfig().getNamesrvAddr();
}
log.info(tip);
System.out.printf("%s%n", tip);
return controller;
} catch (Throwable e) {
e.printStackTrace();
System.exit(-1);
}
return null;
}
在start()中有一端代码
BrokerController.this.registerBrokerAll(true, false, brokerConfig.isForceRegister());
继续往下走,registerBrokerAll()方法中逻辑
doRegisterBrokerAll(checkOrderConfig, oneway, topicConfigWrapper);
doRegisterBrokerAll()的入口逻辑
List<RegisterBrokerResult> registerBrokerResultList = this.brokerOuterAPI.registerBrokerAll(
this.brokerConfig.getBrokerClusterName(),
this.getBrokerAddr(),
this.brokerConfig.getBrokerName(),
this.brokerConfig.getBrokerId(),
this.getHAServerAddr(),
topicConfigWrapper,
this.filterServerManager.buildNewFilterServerList(),
oneway,
this.brokerConfig.getRegisterBrokerTimeoutMills(),
this.brokerConfig.isCompressedRegister());
继续registerBrokerAll()中的入口逻辑
RegisterBrokerResult result = registerBroker(namesrvAddr, oneway, timeoutMills, requestHeader, body);
在registerBroker()这个方法中又有但具体的逻辑,就是写入请求code是103的逻辑,把代码贴出来看。
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.REGISTER_BROKER, requestHeader);
request.setBody(body);
if (oneway) {
try {
this.remotingClient.invokeOneway(namesrvAddr, request, timeoutMills);
} catch (RemotingTooMuchRequestException e) {
// Ignore
}
return null;
}
其中RequestCode.REGISTER_BROKER值就是103。具体是通过netty请求nameserver的DefaultRequestProcessor().processRequest()方法,这样我们就知道具体的请求是哪里来的了。
那继续接着学习registerBrokerWithFilterServer()方法。此方法主要是将新的broker注册到NameSrv上,并处理此broker上的topic信息。
public RemotingCommand registerBrokerWithFilterServer(ChannelHandlerContext ctx, RemotingCommand request)
throws RemotingCommandException {
//初始化响应对象
final RemotingCommand response = RemotingCommand.createResponseCommand(RegisterBrokerResponseHeader.class);
final RegisterBrokerResponseHeader responseHeader = (RegisterBrokerResponseHeader) response.readCustomHeader();
final RegisterBrokerRequestHeader requestHeader =
(RegisterBrokerRequestHeader) request.decodeCommandCustomHeader(RegisterBrokerRequestHeader.class);
//校验请求body的长度
if (!checksum(ctx, request, requestHeader)) {
response.setCode(ResponseCode.SYSTEM_ERROR);
response.setRemark("crc32 not match");
return response;
}
RegisterBrokerBody registerBrokerBody = new RegisterBrokerBody();
if (request.getBody() != null) {
try {
//解析设置topic信息和filterServerList,并设置到broker信息
registerBrokerBody = RegisterBrokerBody.decode(request.getBody(), requestHeader.isCompressed());
} catch (Exception e) {
throw new RemotingCommandException("Failed to decode RegisterBrokerBody", e);
}
} else {
registerBrokerBody.getTopicConfigSerializeWrapper().getDataVersion().setCounter(new AtomicLong(0));
registerBrokerBody.getTopicConfigSerializeWrapper().getDataVersion().setTimestamp(0);
}
//根据上边解析的信息注册broker到nameserver中
RegisterBrokerResult result = this.namesrvController.getRouteInfoManager().registerBroker(
requestHeader.getClusterName(),
requestHeader.getBrokerAddr(),
requestHeader.getBrokerName(),
requestHeader.getBrokerId(),
requestHeader.getHaServerAddr(),
registerBrokerBody.getTopicConfigSerializeWrapper(),
registerBrokerBody.getFilterServerList(),
ctx.channel());
responseHeader.setHaServerAddr(result.getHaServerAddr());
responseHeader.setMasterAddr(result.getMasterAddr());
byte[] jsonValue = this.namesrvController.getKvConfigManager().getKVListByNamespace(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG);
response.setBody(jsonValue);
response.setCode(ResponseCode.SUCCESS);
response.setRemark(null);
return response;
}
主要看下边这个方法
//根据上边解析的信息注册broker到nameserver中
RegisterBrokerResult result = this.namesrvController.getRouteInfoManager().registerBroker(
requestHeader.getClusterName(),
requestHeader.getBrokerAddr(),
requestHeader.getBrokerName(),
requestHeader.getBrokerId(),
requestHeader.getHaServerAddr(),
registerBrokerBody.getTopicConfigSerializeWrapper(),
registerBrokerBody.getFilterServerList(),
ctx.channel());
registerBroker()方法代码
public RegisterBrokerResult registerBroker(
final String clusterName,
final String brokerAddr,
final String brokerName,
final long brokerId,
final String haServerAddr,
final TopicConfigSerializeWrapper topicConfigWrapper,
final List<String> filterServerList,
final Channel channel) {
RegisterBrokerResult result = new RegisterBrokerResult();
try {
try {
//并发锁
this.lock.writeLock().lockInterruptibly();
//获取当前集群的broker集合,并把当前broker注册到集群中
Set<String> brokerNames = this.clusterAddrTable.computeIfAbsent(clusterName, k -> new HashSet<>());
brokerNames.add(brokerName);
boolean registerFirst = false;
//初次注册,把broker信息注册到brokerAddrTable中
BrokerData brokerData = this.brokerAddrTable.get(brokerName);
if (null == brokerData) {
registerFirst = true;
brokerData = new BrokerData(clusterName, brokerName, new HashMap<>());
this.brokerAddrTable.put(brokerName, brokerData);
}
//遍历brokerAddrs,master和salve
Map<Long, String> brokerAddrsMap = brokerData.getBrokerAddrs();
//Switch slave to master: first remove <1, IP:PORT> in namesrv, then add <0, IP:PORT>
//The same IP:PORT must only have one record in brokerAddrTable
Iterator<Entry<Long, String>> it = brokerAddrsMap.entrySet().iterator();
while (it.hasNext()) {
Entry<Long, String> item = it.next();
//broker的地址相同,但是brokerId发生了改变,发生了主从切换,把当前节点从brokerAddrTable删除
if (null != brokerAddr && brokerAddr.equals(item.getValue()) && brokerId != item.getKey()) {
log.debug("remove entry {} from brokerData", item);
it.remove();
}
}
//把当前broker信息注册到brokerAddrTable
String oldAddr = brokerData.getBrokerAddrs().put(brokerId, brokerAddr);
if (MixAll.MASTER_ID == brokerId) {
log.info("cluster [{}] brokerName [{}] master address change from {} to {}",
brokerData.getCluster(), brokerData.getBrokerName(), oldAddr, brokerAddr);
}
//如果oldAddr是空,说明是首次注册
registerFirst = registerFirst || (null == oldAddr);
//当前broker为master
if (null != topicConfigWrapper
&& MixAll.MASTER_ID == brokerId) {
//如果topic信息发生改变,或者broker为初次注册,更新topic信息
if (this.isBrokerTopicConfigChanged(brokerAddr, topicConfigWrapper.getDataVersion())
|| registerFirst) {
ConcurrentMap<String, TopicConfig> tcTable =
topicConfigWrapper.getTopicConfigTable();
if (tcTable != null) {
for (Map.Entry<String, TopicConfig> entry : tcTable.entrySet()) {
this.createAndUpdateQueueData(brokerName, entry.getValue());
}
}
}
}
BrokerLiveInfo prevBrokerLiveInfo = this.brokerLiveTable.put(brokerAddr,
new BrokerLiveInfo(
System.currentTimeMillis(),
topicConfigWrapper.getDataVersion(),
channel,
haServerAddr));
if (null == prevBrokerLiveInfo) {
log.info("new broker registered, {} HAServer: {}", brokerAddr, haServerAddr);
}
if (filterServerList != null) {
if (filterServerList.isEmpty()) {
this.filterServerTable.remove(brokerAddr);
} else {
this.filterServerTable.put(brokerAddr, filterServerList);
}
}
//当前broker非master
if (MixAll.MASTER_ID != brokerId) {
String masterAddr = brokerData.getBrokerAddrs().get(MixAll.MASTER_ID);
if (masterAddr != null) {
BrokerLiveInfo brokerLiveInfo = this.brokerLiveTable.get(masterAddr);
if (brokerLiveInfo != null) {
//把master信息填充到返回
result.setHaServerAddr(brokerLiveInfo.getHaServerAddr());
result.setMasterAddr(masterAddr);
}
}
}
} finally {
this.lock.writeLock().unlock();
}
} catch (Exception e) {
log.error("registerBroker Exception", e);
}
return result;
}
注册的逻辑主要是在这里,这里主要做了这么几个事情
- 把broker注册到clusterAddrTable中
- 把broker信息注册到brokerAddrTable中
- 如果topic信息发生改变,或者broker为初次注册,更新topic信息,并注册到topicQueueTable
- 把brokerAddr注册到brokerLiveTable
这里涉及到四个map,我们看看这几个map是干嘛的
// 所有topic的路由信息,消息发送时根据该路由表进行负载均衡。一个QueueData对象,代表一组broker(包含主从broker)
private final HashMap<String/* topic */, Map<String /* brokerName */ , QueueData>> topicQueueTable;
// broker基础信息:集群名称、brokerName、主从broker地址
private final HashMap<String/* brokerName */, BrokerData> brokerAddrTable;
// 把broker信息注册到集群中
private final HashMap<String/* clusterName */, Set<String/* brokerName */>> clusterAddrTable;
// 各个broker的状态信息,每次收到broker的心跳后,会更新该信息
private final HashMap<String/* brokerAddr */, BrokerLiveInfo> brokerLiveTable;
以上就是注册的这个逻辑,这里主要做的是注册broke和topic信息到各个map中。
getRouteInfoByTopic() 根据topic拉取路由信息
在getRouteInfoByTopic() 方法中,核心方法
//获取topic获取相关信息
TopicRouteData topicRouteData = this.namesrvController.getRouteInfoManager().pickupTopicRouteData(requestHeader.getTopic());
进入方法
//根据topic获取topic相关的信息
public TopicRouteData pickupTopicRouteData(final String topic) {
TopicRouteData topicRouteData = new TopicRouteData();
boolean foundQueueData = false;
boolean foundBrokerData = false;
Set<String> brokerNameSet = new HashSet<>();
List<BrokerData> brokerDataList = new LinkedList<>();
topicRouteData.setBrokerDatas(brokerDataList);
HashMap<String, List<String>> filterServerMap = new HashMap<>();
topicRouteData.setFilterServerTable(filterServerMap);
try {
try {
this.lock.readLock().lockInterruptibly();
//根据topic获取所有所属的broker信息
Map<String, QueueData> queueDataMap = this.topicQueueTable.get(topic);
if (queueDataMap != null) {
topicRouteData.setQueueDatas(new ArrayList<>(queueDataMap.values()));
foundQueueData = true;
brokerNameSet.addAll(queueDataMap.keySet());
//获取当前topic所属broker信息
for (String brokerName : brokerNameSet) {
BrokerData brokerData = this.brokerAddrTable.get(brokerName);
if (null != brokerData) {
BrokerData brokerDataClone = new BrokerData(brokerData.getCluster(), brokerData.getBrokerName(), (HashMap<Long, String>) brokerData
.getBrokerAddrs().clone());
brokerDataList.add(brokerDataClone);
foundBrokerData = true;
// skip if filter server table is empty
if (!filterServerTable.isEmpty()) {
for (final String brokerAddr : brokerDataClone.getBrokerAddrs().values()) {
List<String> filterServerList = this.filterServerTable.get(brokerAddr);
// only add filter server list when not null
if (filterServerList != null) {
filterServerMap.put(brokerAddr, filterServerList);
}
}
}
}
}
}
} finally {
this.lock.readLock().unlock();
}
} catch (Exception e) {
log.error("pickupTopicRouteData Exception", e);
}
log.debug("pickupTopicRouteData {} {}", topic, topicRouteData);
if (foundBrokerData && foundQueueData) {
return topicRouteData;
}
return null;
}
可以看到,这个方法主要是从各种注册表中获取topic的详细信息,所属broker的详细信息,以及filterServerTable详细信息。
scanNotActiveBroker
在启动类NamesrvStartup()方法的初始化方法中,会启动个定时任务会调用scanNotActiveBroker
// 定时任务:每10秒扫描一次broker,移除处于不活跃状态的broker
this.scheduledExecutorService.scheduleAtFixedRate(NamesrvController.this.routeInfoManager::scanNotActiveBroker, 5, 10, TimeUnit.SECONDS);
查看scanNotActiveBroker()方法
/**
* 遍历broker集合,将不活跃的broker移除
* 最新一次的心跳时间超过允许时间,则判定为离线
* 将离线的broker从路由信息中移除;关闭TCP连接
* 被{@link NamesrvController#initialize()}中的定时任务调用
*/
public int scanNotActiveBroker() {
int removeCount = 0;
Iterator<Entry<String, BrokerLiveInfo>> it = this.brokerLiveTable.entrySet().iterator();
while (it.hasNext()) {
Entry<String, BrokerLiveInfo> next = it.next();
long last = next.getValue().getLastUpdateTimestamp();
if ((last + BROKER_CHANNEL_EXPIRED_TIME) < System.currentTimeMillis()) {
RemotingUtil.closeChannel(next.getValue().getChannel());
it.remove();
log.warn("The broker channel expired, {} {}ms", next.getKey(), BROKER_CHANNEL_EXPIRED_TIME);
//调用netty方法销毁链接
this.onChannelDestroy(next.getKey(), next.getValue().getChannel());
removeCount++;
}
}
return removeCount;
}
可以看到此方法就是去除不活跃broker的。定时任务每10秒扫描注册表brokerLiveTable,最后一此心跳时间超过允许时间,则从注册表中删除。onChannelDestroy()方法中是删除所有注册表中此broker信息,详细信息可以查看源码。
总结
- namesrv这两个类是入口,一是:NamesrvStartup启动类,这里会加载配置信息,初始化定时任务;二是:DefaultRequestProcessor类,这个类承接所有的请求。
- 通过类BrokerHousekeepingService我们也知道RocketMQ通信是通过netty实现的,后期也可以学习下netty相关的源码。
- RouteInfoManager路由类是主要的逻辑所在地,对cluster、broker、topic的操作主要在这个类中。这个类中也维护着各个注册表,以及对其的各种操作,比如:registerBroker()注册broker、getRouteInfoByTopic()根据topic获取相关路由信息等。
- 我们也知道了各个注册表就是HashMap,各种注册,取消注册等都是对HashMap的各种操作。这种操作涉及到各种并发操作,底层是使用ReadWriteLock锁来实现并发操作的。
- 在nameServer启动的时候,会启动一个定时任务每十秒清除不活跃broker
- 注册broker的同时会更新topic信息,发生主从切换的时候也会更新集群信息