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