RocketMQ架构学习二:NameServer

主要功能

NameServer是一个简单的 Topic 路由注册中心,支持 Topic、Broker 的动态注册与发现。

主要包括两个功能:

  1. Broker管理,NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制,检查Broker是否还存活;
  2. 路由信息管理,每个NameServer将保存关于 Broker
    集群的整个路由信息和用于客户端查询的队列信息。Producer和Consumer通过NameServer就可以知道整个Broker集群的路由信息,从而进行消息的投递和消费。

NameServer通常会有多个实例部署,各实例间相互不进行信息通讯。Broker是向每一台NameServer注册自己的路由信息,所以每一个NameServer实例上面都保存一份完整的路由信息。当某个NameServer因某种原因下线了,客户端仍然可以向其它NameServer获取路由信息。

源码解析

在这里插入图片描述

目录结构及各类作用
  1. NamesrvStartup:namesrv启动类,加载配置文件信息并初始化NamesrvController
  2. NamesrvController:NameServer核心控制器,里边包含初始化方法和配置信息
  3. KVConfigManager:包含NamesrvController和一个HashMap,主要是要来存储参数信息
  4. KVConfigSerializeWrapper:配合KVConfigManager使用
  5. ClusterTestRequestProcessor:功能类似DefaultRequestProcessor,当参数clusterTest为true是走这个逻辑,否则走DefaultRequestProcessor
  6. DefaultRequestProcessor: 请求处理器,比如获取topic信息,注册broker等
  7. BrokerHousekeepingService:实现ChannelEventListener接口,监听channel事件并作出相应的处理。比如某个broker关闭了,会触发channel事件,把broker注册信息从nameserver删除
  8. BrokerLiveInfo:broker信息模型,存储一些broker信息,此类主要给RouteInfoManager使用
  9. 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初始化逻辑。

  1. 加载了nameserconfig配置信息到configTable中
  2. 加载了nettyServerConfig配置信息到NettyRemotingServer
  3. 为NettyRequestProcessor分配线程池
  4. 创建定时任务:每10秒扫描一次broker,移除处于不活跃状态的broker
  5. 创建定时任务:每10分钟打印一次KV配置,也就是namesrv参数信息
  6. 注册一个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;
    }

注册的逻辑主要是在这里,这里主要做了这么几个事情

  1. 把broker注册到clusterAddrTable中
  2. 把broker信息注册到brokerAddrTable中
  3. 如果topic信息发生改变,或者broker为初次注册,更新topic信息,并注册到topicQueueTable
  4. 把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信息,详细信息可以查看源码。

总结
  1. namesrv这两个类是入口,一是:NamesrvStartup启动类,这里会加载配置信息,初始化定时任务;二是:DefaultRequestProcessor类,这个类承接所有的请求。
  2. 通过类BrokerHousekeepingService我们也知道RocketMQ通信是通过netty实现的,后期也可以学习下netty相关的源码。
  3. RouteInfoManager路由类是主要的逻辑所在地,对cluster、broker、topic的操作主要在这个类中。这个类中也维护着各个注册表,以及对其的各种操作,比如:registerBroker()注册broker、getRouteInfoByTopic()根据topic获取相关路由信息等。
  4. 我们也知道了各个注册表就是HashMap,各种注册,取消注册等都是对HashMap的各种操作。这种操作涉及到各种并发操作,底层是使用ReadWriteLock锁来实现并发操作的。
  5. 在nameServer启动的时候,会启动一个定时任务每十秒清除不活跃broker
  6. 注册broker的同时会更新topic信息,发生主从切换的时候也会更新集群信息

上一篇:RocketMQ架构学习一:前序
下一篇:RocketMQ源码学习三:Broker基础知识之消息存储

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值