• 源码分析-NameServer


    架构设计

    消息中间件的设计思路一般是基于主题订阅发布的机制,消息生产者(Producer)发送某一个主题到消息服务器,消息服务器负责将消息持久化存储,消息消费者(Consumer)订阅该兴趣的主题,消息服务器根据订阅信息(路由信息)将消息推送到消费者(Push模式)或者消费者主动向消息服务器拉取(Pull模式),从而实现消息生产者与消息消费者解耦。为了避免消息服务器的单点故障导致的整个系统瘫痪,通常会部署多台消息服务器共同承担消息的存储。那消息生产者如何知道消息要发送到哪台消息服务器呢?如果某一台消息服务器宕机了,那么消息生产者如何在不重启服务情况下感知呢?NameServer就是为了解决以上问题设计的。

    image

    Broker消息服务器在启动的时向所有NameServer注册,消息生产者(Producer)在发送消息时之前先从NameServer获取Broker服务器地址列表,然后根据负载均衡算法从列表中选择一台服务器进行发送NameServer与每台Broker保持长连接,并间隔30S检测Broker是否存活,如果检测到Broker宕机,则从路由注册表中删除。但是路由变化不会马上通知消息生产者。这样设计的目的是为了降低NameServer实现的复杂度,在消息发送端提供容错机制保证消息发送的可用性

    NameServer本身的高可用是通过部署多台NameServer来实现,但彼此之间不通讯,也就是NameServer服务器之间在某一个时刻的数据并不完全相同,但这对消息发送并不会造成任何影响,这也是NameServer设计的一个亮点,总之,RocketMQ设计追求简单高效。

    启动流程

    image

    启动类: org.apache.rocketmq.namesrv.NamesrvStartup

    步骤一

    解析配置文件,填充NameServerConfig、NettyServerConfig属性值,并创建NamesrvController

    代码:NamesrvController#createNamesrvController

    public static NamesrvController createNamesrvController(String[] args) throws IOException, JoranException {
            System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, Integer.toString(MQVersion.CURRENT_VERSION));
            //PackageConflictDetect.detectFastjson();
    
            Options options = ServerUtil.buildCommandlineOptions(new Options());
            commandLine = ServerUtil.parseCmdLine("mqnamesrv", args, buildCommandlineOptions(options), new PosixParser());
            if (null == commandLine) {
                System.exit(-1);
                return null;
            }
    		//创建NamesrvConfig
            final NamesrvConfig namesrvConfig = new NamesrvConfig();
    		//创建NettyServerConfig
            final NettyServerConfig nettyServerConfig = new NettyServerConfig();
    		//设置启动端口号
            nettyServerConfig.setListenPort(9876);
    		//解析启动-c参数
            if (commandLine.hasOption('c')) {
                String file = commandLine.getOptionValue('c');
                if (file != null) {
                    InputStream in = new BufferedInputStream(new FileInputStream(file));
                    properties = new Properties();
                    properties.load(in);
                    MixAll.properties2Object(properties, namesrvConfig);
                    MixAll.properties2Object(properties, nettyServerConfig);
    
                    namesrvConfig.setConfigStorePath(file);
    
                    System.out.printf("load config properties file OK, %s%n", file);
                    in.close();
                }
            }
    		//解析启动-p参数
            if (commandLine.hasOption('p')) {
                InternalLogger console = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_CONSOLE_NAME);
                MixAll.printObjectProperties(console, namesrvConfig);
                MixAll.printObjectProperties(console, nettyServerConfig);
                System.exit(0);
            }
    		//将启动参数填充到namesrvConfig,nettyServerConfig
            MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), namesrvConfig);
    
            if (null == namesrvConfig.getRocketmqHome()) {
                System.out.printf("Please set the %s variable in your environment to match the location of the RocketMQ installation%n", MixAll.ROCKETMQ_HOME_ENV);
                System.exit(-2);
            }
    
            LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
            JoranConfigurator configurator = new JoranConfigurator();
            configurator.setContext(lc);
            lc.reset();
            configurator.doConfigure(namesrvConfig.getRocketmqHome() + "/conf/logback_namesrv.xml");
    
            log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);
    
            MixAll.printObjectProperties(log, namesrvConfig);
            MixAll.printObjectProperties(log, nettyServerConfig);
    		//创建NameServerController
            final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig);
    
            // remember all configs to prevent discard
            controller.getConfiguration().registerConfig(properties);
    
            return controller;
        }
    

    NamesrvConfig属性

    private String rocketmqHome = System.getProperty(MixAll.ROCKETMQ_HOME_PROPERTY, System.getenv(MixAll.ROCKETMQ_HOME_ENV));
    private String kvConfigPath = System.getProperty("user.home") + File.separator + "namesrv" + File.separator + "kvConfig.json";
    private String configStorePath = System.getProperty("user.home") + File.separator + "namesrv" + File.separator + "namesrv.properties";
    private String productEnvName = "center";
    private boolean clusterTest = false;
    private boolean orderMessageEnable = false;
    

    image

    • rocketmqHome:rocketmq主目录
    • kvConfig:NameServer存储KV配置属性的持久化路径
    • configStorePath:nameServer默认配置文件路径
    • orderMessageEnable:是否支持顺序消息

    NettyServerConfig属性

    private int listenPort = 8888;
    private int serverWorkerThreads = 8;
    private int serverCallbackExecutorThreads = 0;
    private int serverSelectorThreads = 3;
    private int serverOnewaySemaphoreValue = 256;
    private int serverAsyncSemaphoreValue = 64;
    private int serverChannelMaxIdleTimeSeconds = 120;
    private int serverSocketSndBufSize = NettySystemConfig.socketSndbufSize;
    private int serverSocketRcvBufSize = NettySystemConfig.socketRcvbufSize;
    private boolean serverPooledByteBufAllocatorEnable = true;
    private boolean useEpollNativeSelector = false;
    

    image

    • listenPort:NameServer监听端口,该值默认会被初始化为9876
    • serverWorkerThreads:Netty业务线程池线程个数
    • serverCallbackExecutorThreads:Netty public任务线程池线程个数,Netty网络设计,根据业务类型会创建不同的线程池,比如处理消息发送、消息消费、心跳检测等。如果该业务类型未注册线程池,则由public线程池执行。
    • serverSelectorThreads:IO线程池个数,主要是NameServer、Broker端解析请求、返回相应的线程个数,这类线程主要是处理网路请求的,解析请求包,然后转发到各个业务线程池完成具体的操作,然后将结果返回给调用方;
    • serverOnewaySemaphoreValue:send oneway消息请求并发读(Broker端参数);
    • serverAsyncSemaphoreValue:异步消息发送最大并发度;
    • serverChannelMaxIdleTimeSeconds :网络连接最大的空闲时间,默认120s。
    • serverSocketSndBufSize:网络socket发送缓冲区大小。
    • serverSocketRcvBufSize: 网络接收端缓存区大小。
    • serverPooledByteBufAllocatorEnable:ByteBuffer是否开启缓存;
    • useEpollNativeSelector:是否启用Epoll IO模型。

    步骤二

    根据启动属性创建NamesrvController实例,并初始化该实例。NameServerController实例为NameServer核心控制器

    代码:NamesrvController#initialize

        public boolean initialize() {
    		//加载KV配置
            this.kvConfigManager.load();
    		//创建NettyServer网络处理对象
            this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);
    		//开启定时任务:每隔10s扫描一次Broker,移除不活跃的Broker
            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);
    		//开启定时任务:每隔10min打印一次KV配置
            this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    
                @Override
                public void run() {
                    NamesrvController.this.kvConfigManager.printAllPeriodically();
                }
            }, 1, 10, TimeUnit.MINUTES);
    
            if (TlsSystemConfig.tlsMode != TlsMode.DISABLED) {
                // Register a listener to reload SslContext
                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;
        }
    

    步骤三

    在JVM进程关闭之前,先将线程池关闭,及时释放资源

    代码:NamesrvStartup#start

    Runtime.getRuntime().addShutdownHook(new ShutdownHookThread(log, new Callable<Void>() {
    	@Override
    	public Void call() throws Exception {
    	controller.shutdown();
    		return null;
    	}
    }));
    

    路由管理

    NameServer的主要作用是为消息的生产者和消息消费者提供关于主题Topic的路由信息,那么NameServer需要存储路由的基础信息,还要管理Broker节点,包括路由注册、路由删除等

    路由元信息

    代码:RouteInfoManager

    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;
    

    image

    • topicQueueTable:Topic消息队列路由信息,消息发送时根据路由表进行负载均衡
    • brokerAddrTable:Broker基础信息,包括brokerName、所属集群名称、主备Broker地址
    • clusterAddrTable:Broker集群信息,存储集群中所有Broker名称
    • brokerLiveTable:Broker状态信息,NameServer每次收到心跳包是会替换该信息
    • filterServerTable:Broker上的FilterServer列表,用于类模式消息过滤。

    RocketMQ基于订阅发布机制,一个Topic拥有多个消息队列,一个Broker为每一个主题创建4个读队列和4个写队列。多个Broker组成一个集群,集群由相同的多台Broker组成Master-Slave架
    构,brokerId为0代表Master,大于0为Slave。BrokerLiveInfo中的lastUpdateTimestamp存储上次收到Broker心跳包的时间。

    image

    image

    路由注册

    发送心跳包

    image

    RocketMQ路由注册是通过Broker与NameServer的心跳功能实现的。Broker启动时向集群中所有的NameServer发送心跳信息,每隔30s向集群中所有NameServer发送心跳包,NameServer收到心跳包时会更新brokerLiveTable缓存中BrokerLiveInfo的lastUpdataTimeStamp信息,然后NameServer每隔10s扫描brokerLiveTable,如果连续120S没有收到心跳包,NameServer将移除Broker的路由信息同时关闭Socket连接

    代码:BrokerController#start

            this.registerBrokerAll(true, false, true);
    
            this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    
                @Override
                public void run() {
                    try {
                        BrokerController.this.registerBrokerAll(true, false, brokerConfig.isForceRegister());
                    } catch (Throwable e) {
                        log.error("registerBrokerAll Exception", e);
                    }
                }
            }, 1000 * 10, Math.max(10000, Math.min(brokerConfig.getRegisterNameServerPeriod(), 60000)), TimeUnit.MILLISECONDS);
    

    代码:BrokerOuterAPI#registerBrokerAll

        public List<RegisterBrokerResult> registerBrokerAll(
            final String clusterName,
            final String brokerAddr,
            final String brokerName,
            final long brokerId,
            final String haServerAddr,
            final TopicConfigSerializeWrapper topicConfigWrapper,
            final List<String> filterServerList,
            final boolean oneway,
            final int timeoutMills,
            final boolean compressed) {
    
            final List<RegisterBrokerResult> registerBrokerResultList = Lists.newArrayList();
    		//获得nameServer地址信息
            List<String> nameServerAddressList = this.remotingClient.getNameServerAddressList();
    		//遍历所有nameserver列表
            if (nameServerAddressList != null && nameServerAddressList.size() > 0) {
    			//封装请求头
                final RegisterBrokerRequestHeader requestHeader = new RegisterBrokerRequestHeader();
                requestHeader.setBrokerAddr(brokerAddr);
                requestHeader.setBrokerId(brokerId);
                requestHeader.setBrokerName(brokerName);
                requestHeader.setClusterName(clusterName);
                requestHeader.setHaServerAddr(haServerAddr);
                requestHeader.setCompressed(compressed);
    			//封装请求体
                RegisterBrokerBody requestBody = new RegisterBrokerBody();
                requestBody.setTopicConfigSerializeWrapper(topicConfigWrapper);
                requestBody.setFilterServerList(filterServerList);
                final byte[] body = requestBody.encode(compressed);
                final int bodyCrc32 = UtilAll.crc32(body);
                requestHeader.setBodyCrc32(bodyCrc32);
                final CountDownLatch countDownLatch = new CountDownLatch(nameServerAddressList.size());
                for (final String namesrvAddr : nameServerAddressList) {
                    brokerOuterExecutor.execute(new Runnable() {
                        @Override
                        public void run() {
                            try {
    							//分别向NameServer注册
                                RegisterBrokerResult result = registerBroker(namesrvAddr,oneway, timeoutMills,requestHeader,body);
                                if (result != null) {
                                    registerBrokerResultList.add(result);
                                }
    
                                log.info("register broker[{}]to name server {} OK", brokerId, namesrvAddr);
                            } catch (Exception e) {
                                log.warn("registerBroker Exception, {}", namesrvAddr, e);
                            } finally {
                                countDownLatch.countDown();
                            }
                        }
                    });
                }
    
                try {
                    countDownLatch.await(timeoutMills, TimeUnit.MILLISECONDS);
                } catch (InterruptedException e) {
                }
            }
    
            return registerBrokerResultList;
        }
    

    代码:BrokerOutAPI#registerBroker

            if (oneway) {
                try {
                    this.remotingClient.invokeOneway(namesrvAddr, request, timeoutMills);
                } catch (RemotingTooMuchRequestException e) {
                    // Ignore
                }
                return null;
            }
    
            RemotingCommand response = this.remotingClient.invokeSync(namesrvAddr, request, timeoutMills);
    
    处理心跳包

    image

    org.apache.rocketmq.namesrv.processor.DefaultRequestProcessor 网路处理类解析请求类型,
    如果请求类型是为REGISTER_BROKER,则将请求转发到 RouteInfoManager#regiesterBroker

    代码:DefaultRequestProcessor#processRequest

    //判断是注册Broker信息
    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 {
    		//注册Broker信息
    		return this.registerBroker(ctx, request);
    	}
    

    代码:DefaultRequestProcessor#registerBroker

            RegisterBrokerResult result = this.namesrvController.getRouteInfoManager().registerBroker(
                requestHeader.getClusterName(),
                requestHeader.getBrokerAddr(),
                requestHeader.getBrokerName(),
                requestHeader.getBrokerId(),
                requestHeader.getHaServerAddr(),
                registerBrokerBody.getTopicConfigSerializeWrapper(),
                registerBrokerBody.getFilterServerList(),
                ctx.channel());
    

    代码:RouteInfoManager#registerBroker
    维护路由信息

    //加锁
    this.lock.writeLock().lockInterruptibly();
    //维护clusterAddrTable
    Set<String> brokerNames = this.clusterAddrTable.get(clusterName);
    if (null == brokerNames) {
    	brokerNames = new HashSet<String>();
    	this.clusterAddrTable.put(clusterName, brokerNames);
    } 
    brokerNames.add(brokerName);
    
    //维护brokerAddrTable
    BrokerData brokerData = this.brokerAddrTable.get(brokerName);
    //第一次注册,则创建brokerData
    if (null == brokerData) {
    	registerFirst = true;
    	brokerData = new BrokerData(clusterName, brokerName, new HashMap<Long,String>());
    	this.brokerAddrTable.put(brokerName, brokerData);
    }
    //非第一次注册,更新Broker
    Map<Long, String> brokerAddrsMap = brokerData.getBrokerAddrs();
    Iterator<Entry<Long, String>> it = brokerAddrsMap.entrySet().iterator();
    while (it.hasNext()) {
    	Entry<Long, String> item = it.next();
    	if (null != brokerAddr && brokerAddr.equals(item.getValue()) && brokerId != item.getKey()) {
    		it.remove();
    	}
    }
    String oldAddr = brokerData.getBrokerAddrs().put(brokerId, brokerAddr);
    registerFirst = registerFirst || (null == oldAddr);
    //维护topicQueueTable
    if (null != topicConfigWrapper && MixAll.MASTER_ID == brokerId) {
    	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());
    			}
    		}
    	}
    }
    

    代码:RouteInfoManager#createAndUpdateQueueData

        private void createAndUpdateQueueData(final String brokerName, final TopicConfig topicConfig) {
    		//创建QueueData
            QueueData queueData = new QueueData();
            queueData.setBrokerName(brokerName);
            queueData.setWriteQueueNums(topicConfig.getWriteQueueNums());
            queueData.setReadQueueNums(topicConfig.getReadQueueNums());
            queueData.setPerm(topicConfig.getPerm());
            queueData.setTopicSynFlag(topicConfig.getTopicSysFlag());
    		//获得topicQueueTable中队列集合
            List<QueueData> queueDataList = this.topicQueueTable.get(topicConfig.getTopicName());
    		//topicQueueTable为空,则直接添加queueData到队列集合
            if (null == queueDataList) {
                queueDataList = new LinkedList<QueueData>();
                queueDataList.add(queueData);
                this.topicQueueTable.put(topicConfig.getTopicName(), queueDataList);
                log.info("new topic registered, {} {}", topicConfig.getTopicName(), queueData);
            } else {
    			//判断是否是新的队列
                boolean addNewOne = true;
                Iterator<QueueData> it = queueDataList.iterator();
                while (it.hasNext()) {
                    QueueData qd = it.next();
    				//如果brokerName相同,代表不是新的队列
                    if (qd.getBrokerName().equals(brokerName)) {
                        if (qd.equals(queueData)) {
                            addNewOne = false;
                        } else {
                            log.info("topic changed, {} OLD: {} NEW: {}", topicConfig.getTopicName(), qd,
                                queueData);
                            it.remove();
                        }
                    }
                }
    			//如果是新的队列,则添加队列到queueDataList
                if (addNewOne) {
                    queueDataList.add(queueData);
                }
            }
        }
    
    //维护brokerLiveTable
    BrokerLiveInfo prevBrokerLiveInfo = this.brokerLiveTable.put(brokerAddr,
                        new BrokerLiveInfo(
                            System.currentTimeMillis(),
                            topicConfigWrapper.getDataVersion(),
                            channel,
                            haServerAddr));
    
    				//维护filterServerList
                    if (filterServerList != null) {
                        if (filterServerList.isEmpty()) {
                            this.filterServerTable.remove(brokerAddr);
                        } else {
                            this.filterServerTable.put(brokerAddr, filterServerList);
                        }
                    }
    
                    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) {
                                result.setHaServerAddr(brokerLiveInfo.getHaServerAddr());
                                result.setMasterAddr(masterAddr);
                            }
                        }
                    }
    
    路由删除

    Broker 每隔30s向 NameServer 发送一个心跳包,心跳包包含 BrokerIdBroker 地址, Broker 名称, Broker 所属集群名称、 Broker 关联的 FilterServer 列表。但是如果 Broker 宕机,NameServer 无法收到心跳包,此时 NameServer 如何来剔除这些失效的 Broker 呢? NameServer 会每隔10s扫描 brokerLiveTable 状态表,如果 BrokerLivelastUpdateTimestamp的时间戳距当前时间超过120s,则认为 Broker 失效,移除该 Broker ,关闭与 Broker 连接,同时更新topicQueueTablebrokerAddrTablebrokerLiveTablefilterServerTable

    RocketMQ有两个触发点来删除路由信息:

    • NameServer定期扫描brokerLiveTable检测上次心跳包与当前系统的时间差,如果时间超过120s,则需要移除broker
    • Broker在正常关闭的情况下,会执行unregisterBroker指令

    这两种方式路由删除的方法都是一样的,就是从相关路由表中删除与该broker相关的信息。

    image

    代码:NamesrvController#initialize

    //每隔10s扫描一次为活跃Broker
    this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    
        @Override
        public void run() {
            NamesrvController.this.routeInfoManager.scanNotActiveBroker();
        }
    }, 5, 10, TimeUnit.SECONDS);
    

    代码:RouteInfoManager#scanNotActiveBroker

    public void scanNotActiveBroker() {
        //获得brokerLiveTable
        Iterator<Entry<String, BrokerLiveInfo>> it = this.brokerLiveTable.entrySet().iterator();
        //遍历brokerLiveTable
        while (it.hasNext()) {
            Entry<String, BrokerLiveInfo> next = it.next();
            long last = next.getValue().getLastUpdateTimestamp();
            //如果收到心跳包的时间距当时时间是否超过120s
            if ((last + BROKER_CHANNEL_EXPIRED_TIME) < System.currentTimeMillis()) {
                //关闭连接
                RemotingUtil.closeChannel(next.getValue().getChannel());
                //移除broker
                it.remove();
                //维护路由表
                this.onChannelDestroy(next.getKey(), next.getValue().getChannel());
            }
        }
    }
    

    代码:RouteInfoManager#onChannelDestroy

    //申请写锁,根据brokerAddress从brokerLiveTable和filterServerTable移除
    this.lock.writeLock().lockInterruptibly();
    this.brokerLiveTable.remove(brokerAddrFound);
    this.filterServerTable.remove(brokerAddrFound);
    
    //维护brokerAddrTable
    String brokerNameFound = null;
    boolean removeBrokerName = false;
    Iterator<Entry<String, BrokerData>> itBrokerAddrTable =this.brokerAddrTable.entrySet().iterator();
    //遍历brokerAddrTable
    while (itBrokerAddrTable.hasNext() && (null == brokerNameFound)) {
        BrokerData brokerData = itBrokerAddrTable.next().getValue();
        //遍历broker地址
        Iterator<Entry<Long, String>> it = brokerData.getBrokerAddrs().entrySet().iterator();
        while (it.hasNext()) {
            Entry<Long, String> entry = it.next();
            Long brokerId = entry.getKey();
            String brokerAddr = entry.getValue();
            //根据broker地址移除brokerAddr
            if (brokerAddr.equals(brokerAddrFound)) {
                brokerNameFound = brokerData.getBrokerName();
                it.remove();
                log.info("remove brokerAddr[{}, {}] from brokerAddrTable, because channel destroyed",
                    brokerId, brokerAddr);
                break;
            }
        }
    	//如果当前主题只包含待移除的broker,则移除该topic
        if (brokerData.getBrokerAddrs().isEmpty()) {
            removeBrokerName = true;
            itBrokerAddrTable.remove();
            log.info("remove brokerName[{}] from brokerAddrTable, because channel destroyed",
                brokerData.getBrokerName());
        }
    }
    
    //维护clusterAddrTable
    if (brokerNameFound != null && removeBrokerName) {
        Iterator<Entry<String, Set<String>>> it = this.clusterAddrTable.entrySet().iterator();
        //遍历clusterAddrTable
        while (it.hasNext()) {
            Entry<String, Set<String>> entry = it.next();
            //获得集群名称
            String clusterName = entry.getKey();
            //获得集群中brokerName集合
            Set<String> brokerNames = entry.getValue();
            //从brokerNames中移除brokerNameFound
            boolean removed = brokerNames.remove(brokerNameFound);
            if (removed) {
                log.info("remove brokerName[{}], clusterName[{}] from clusterAddrTable, because channel destroyed",
                    brokerNameFound, clusterName);
    
                if (brokerNames.isEmpty()) {
                    log.info("remove the clusterName[{}] from clusterAddrTable, because channel destroyed and no broker in this cluster",
                        clusterName);
                    //如果集群中不包含任何broker,则移除该集群
                    it.remove();
                }
    
                break;
            }
        }
    }
    
    //维护topicQueueTable队列
    if (removeBrokerName) {
        //遍历topicQueueTable
        Iterator<Entry<String, List<QueueData>>> itTopicQueueTable =
            this.topicQueueTable.entrySet().iterator();
        while (itTopicQueueTable.hasNext()) {
            Entry<String, List<QueueData>> entry = itTopicQueueTable.next();
            //主题名称
            String topic = entry.getKey();
            //队列集合
            List<QueueData> queueDataList = entry.getValue();
    		//遍历该主题队列
            Iterator<QueueData> itQueueData = queueDataList.iterator();
            while (itQueueData.hasNext()) {
                //从队列中移除为活跃broker信息
                QueueData queueData = itQueueData.next();
                if (queueData.getBrokerName().equals(brokerNameFound)) {
                    itQueueData.remove();
                    log.info("remove topic[{} {}], from topicQueueTable, because channel destroyed",
                        topic, queueData);
                }
            }
    		//如果该topic的队列为空,则移除该topic
            if (queueDataList.isEmpty()) {
                itTopicQueueTable.remove();
                log.info("remove topic[{}] all queue, from topicQueueTable, because channel destroyed",
                    topic);
            }
        }
    }
    
    //释放写锁
    finally {
        this.lock.writeLock().unlock();
    }
    

    路由发现

    RocketMQ路由发现是非实时的,当Topic路由出现变化后,NameServer不会主动推送给客户端,而是由客户端定时拉取主题最新的路由。

    代码:DefaultRequestProcessor#getRouteInfoByTopic

    public RemotingCommand getRouteInfoByTopic(ChannelHandlerContext ctx,
        RemotingCommand request) throws RemotingCommandException {
        final RemotingCommand response = RemotingCommand.createResponseCommand(null);
        final GetRouteInfoRequestHeader requestHeader =
            (GetRouteInfoRequestHeader) request.decodeCommandCustomHeader(GetRouteInfoRequestHeader.class);
    	//调用RouteInfoManager的方法,从路由表topicQueueTable、brokerAddrTable、filterServerTable中分别填充TopicRouteData的List<QueueData>、List<BrokerData>、filterServer
        TopicRouteData topicRouteData = this.namesrvController.getRouteInfoManager().pickupTopicRouteData(requestHeader.getTopic());
    	//如果找到主题对应你的路由信息并且该主题为顺序消息,则从NameServer KVConfig中获取关于顺序消息相关的配置填充路由信息
        if (topicRouteData != null) {
            if (this.namesrvController.getNamesrvConfig().isOrderMessageEnable()) {
                String orderTopicConf =
                    this.namesrvController.getKvConfigManager().getKVConfig(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG,
                        requestHeader.getTopic());
                topicRouteData.setOrderTopicConf(orderTopicConf);
            }
    
            byte[] content = topicRouteData.encode();
            response.setBody(content);
            response.setCode(ResponseCode.SUCCESS);
            response.setRemark(null);
            return response;
        }
    
        response.setCode(ResponseCode.TOPIC_NOT_EXIST);
        response.setRemark("No topic route info in name server for the topic: " + requestHeader.getTopic()
            + FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL));
        return response;
    }
    

    小结

    image

  • 相关阅读:
    Java StringTokenizer Example
    java 删除字符串中的特定字符
    [Python]网络爬虫(二):利用urllib2通过指定的URL抓取网页内容
    Uniform resource name
    [Python]网络爬虫(一):抓取网页的含义和URL基本构成
    coco2dx 精灵类
    window和nodejs作用域区别(待续)
    ubuntu开机遇到-您的当前网络有.local域,我们不建议这样做而且这与AVAHI网络服务探测不兼容。该服务已被禁用
    ruby中的reject和reject!
    ruby中将数组转换成hash
  • 原文地址:https://www.cnblogs.com/weianlai/p/14613686.html
Copyright © 2020-2023  润新知