NameServer是RocketMQ的路由管理、服務註冊與服務發現中心,首先簡要說明一下NameServer的主要功能:
- 所有的Broker服務器會向所有的nameServer註冊自己的信息
- 消息生產者(Producer)在發送消息時,也會向NameServer請求Broker服務器的地址信息,然後從中選擇一臺broker服務器進行消息發送
- NameServer會與Broker服務器保持一個連接,接收Broker定時發送回來的心跳信息,如果超過一定時間沒收到心跳,那麼NameServer將會刪除該Broker的路由註冊信息,但是有一點要注意,NameServer在刪除了某個Broker服務器的路由信息後,並不會主動去通知消息生產者producer,producer自己有一套機制去發現發送消息的Broker服務器已失效,後續在解析生產者代碼時會解析。
- 消費者在消費消息之前,也需要向NameServer請求當前存在的Broker服務器,從而進行消息的訂閱與消費
NameServer的啓動
NameServer的主要功能都在NamesrvController類中,該類內定義瞭如下的對象,大致羅列了所有對象的基本功能:
//日誌
private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);
//NameServer的配置信息
private final NamesrvConfig namesrvConfig;
//NameServer模塊內嵌的Netty服務器的參數
private final NettyServerConfig nettyServerConfig;
//線程池,用於運行一些定時任務
private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl(
"NSScheduledThread"));
//維護NameServer的一些k-v鍵值對
private final KVConfigManager kvConfigManager;
//維護NameServer的所有路由信息,包括topic的隊列信息、Broker的地址等
private final RouteInfoManager routeInfoManager;
//NameServer用於收發消息,接受連接請求的Netty服務器,
private RemotingServer remotingServer;
//處理Broker與NameServer連接狀態的一些響應函數
private BrokerHousekeepingService brokerHousekeepingService;
//處理接收到消息的線程池,該線程池執行的代碼在對應的processor中
private ExecutorService remotingExecutor;
//NameServer的配置信息類,包括NameServer和NettyServer信息的持久化保存
private Configuration configuration;
//檢測認證文件的服務
private FileWatchService fileWatchService;
NameServer啓動時,首先通過createNamesrvController方法創建一個NamesrvController對象,後續的所有操作都是通過該對象來進行。NamesrvController首先調用initialize方法完成對象的初始化,主要包括:
-
this.kvConfigManager.load();
加載鍵值對文件 -
this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);
創建Netty服務器,在創建該服務器時,會傳入一個BrokerHouseKeepingService對象,該對象首先了一系列channel狀態變化時的響應函數,這樣在Broker服務器與NameServer網絡連接狀態發生變化時,BrokerHouseKeepingService對象對應的響應方法將會執行。 -
創建線程池,並定義了兩個定時操作,分別是掃描失活的broker服務器和打印NameServer維護的鍵值對信息。
this.remotingExecutor =
Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), new ThreadFactoryImpl("RemotingExecutorThread_"));
this.registerProcessor();
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
NamesrvController.this.routeInfoManager.scanNotActiveBroker();
}
}, 5, 10, TimeUnit.SECONDS);
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
NamesrvController.this.kvConfigManager.printAllPeriodically();
}
}, 1, 10, TimeUnit.MINUTES);
- 啓動定時檢測認證文件的服務
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();
}
});
在創建完所有的服務對象後,NamesrvController對象調用start方法啓動服務。
NameServer支持的消息處理類型
在之前的文章裏說過,RocketMQ會在processor中定義具體的消息處理代碼,並定義相應的線程池就行消息的處理。在NameServer中,只定義了一個DefaultRequestProcessor,和一個名爲remotingExecutor線程池,因此NameServer能處理的消息類型都定義在DefaultRequestProcessor內,並且這些消息處理代碼都會使用remotingExecutor線程池內的線程來執行。
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_ROUTEINTO_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.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);
從RequestCode的值可以看出,NameServer支持基本的信息查詢,以及Broker服務器地址的註冊、註銷和基於Topic的查詢,體現了NameServer作爲RocketMQ路由管理、服務註冊與發現中心的功能,下面將針對幾個比較重要的功能進行講解。
Broker服務器地址的註冊
由DefaultRequestProcessor的代碼可以看到,當有新的broker服務器註冊消息發送至NameServer時,會調用registerBroker方法,該方法最主要的功能是調用NamesrvController對象的RouteInfoManager對應的registerBroker方法。
RouteInfoManager維護着註冊到NameServer的所有路由信息,從RouteInfoManager的成員對象可以看到,所有的路由信息都以HashMap的方式保存,包括topic包含的隊列信息、Broker服務器的地址信息,集羣地址信息、Broker服務器發送過來的最近一次的心跳信息,服務器過濾信息。
private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);
private final static long BROKER_CHANNEL_EXPIRED_TIME = 1000 * 60 * 2;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final HashMap<String/* topic */, List<QueueData>> topicQueueTable;
private final HashMap<String/* brokerName */, BrokerData> brokerAddrTable;
private final HashMap<String/* clusterName */, Set<String/* brokerName */>> clusterAddrTable;
private final HashMap<String/* brokerAddr */, BrokerLiveInfo> brokerLiveTable;
private final HashMap<String/* brokerAddr */, List<String>/* Filter Server */> filterServerTable;
因此,Broker服務器的註冊,是將Broker服務器的地址信息寫入到brokerAddrTable上,如果該 broker服務器已經註冊過了,那麼新的註冊信息將會頂替掉舊的註冊信息,也就是說,每臺broker服務器在NameServer上僅維護一條註冊信息。
Broker服務器連接情況的維護
NameServer保存着所有註冊的Broker的地址信息,並且會定期掃描失活Broker服務器並刪除。Broker會定期向NameServer發送心跳信息,這個信息並沒有單獨定義一個消息類型,而是合併在Broker的註冊消息上,在RouteInfoManager的registerBroker方法上可以看到,每次收到Broker註冊信息時,會同時更新brokerLiveTable的信息,保存每臺broker最後一次向NameServer發送心跳信息的時間。
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);
}
有了brokerLiveTable的信息,NameServer開了一個定時任務,定時調用RouteInfoManager的scanNotActiveBroker方法,遍歷brokerLiveTable的內容,如果broker超過一定時間(默認爲2分鐘)沒有向NameServer發送心跳信息,那麼就認爲該broker已經失活,從brokerLiveTable刪除該broker的信息。
public void scanNotActiveBroker() {
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);
this.onChannelDestroy(next.getKey(), next.getValue().getChannel());
}
}
}