RocketMQ源碼閱讀筆記-NameSrv功能解析

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());
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章