RocketMQ源码解析——路由发现

106 阅读8分钟

一、服务发现

RocketMQ有下面几个角色

NameSrv: 注册中心

Broker: 消息服务器

Producer: 消息生产者

Consumer: 消息消费者

RocketMQ没有使用Zookeeper作为服务的注册中心,而是自研的NameSrv,每个NameSrv都是无关联的节点。

当消息服务器启动后,会将自己的地址信息等,注册到所有的NameSrv。

image.png

当Producer和Consumer启动后,会主动连接NameServer,获取可用的Broker列表,并选取Broker进行连接,进行消息发送与拉取。

image.png

二、源码分析

2.1 路由注册

在源码的broker包根目录下,有一个BrokerStartup启动类。

image.png

入口代码如下:

  public static void main(String[] args) {
      start(createBrokerController(args));
  }

主要进行了两件事:

  1. 创建BrokerController,用来管理Broker节点
  2. 启动BrokerController

第一步:创建BrokerController过程,主要是分析配置信息,比如:NameSrv集群的地址表、Broker信的角色信息(Master/Salve)等,并对其进行初始化。

  final BrokerController controller = new BrokerController(
      brokerConfig,
      nettyServerConfig,
      nettyClientConfig,
      messageStoreConfig);
  Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
      private volatile boolean hasShutdown = false;
      private AtomicInteger shutdownTimes = new AtomicInteger(0);
  
      @Override
      public void run() {
          synchronized (this) {
              log.info("Shutdown hook was invoked, {}", this.shutdownTimes.incrementAndGet());
              if (!this.hasShutdown) {
                  this.hasShutdown = true;
                  long beginTime = System.currentTimeMillis();
                  controller.shutdown();
                  long consumingTimeTotal = System.currentTimeMillis() - beginTime;
                  log.info("Shutdown hook over, consuming total time(ms): {}", consumingTimeTotal);
              }
          }
      }
  }, "ShutdownHook"));

如果代码中使用了线程池,一种优雅的停机方式是注册一个JVM钩子函数,在JVM进程关闭之前,先将线程池关闭,及时释放资源。

第二步是主要的,启动各种服务。

  public void start() throws Exception {
      if (this.messageStore != null) {
          // 这里的messageStore是在创建controller时初始化的,controller.initialize(); 是DefaultMessageStore类
          // 启动消息存储服务,包括启动Broker的高可用机制;启动以下任务:
          // 1. 启动把内存当中的消息刷到磁盘中的任务
          // 2. 把 commitLog 中的消息分发到 consumerQueue 文件中任务
          // 3. cleanFilesPeriodically(): 清除过期的 commitLog/ consumerQueue 日志文件, 10s
          // 4. checkSelf(): 检查 commitLog/ consumerQueue 的 映射文件,10min
          // 5. 如果 commitLog 锁时间超过了阈值,持久化它的锁信息, 1s
          // 6. isSpaceFull(): 检测磁盘空间是否足够, 10s
          // 需要掌握的java的知识点:scheduleAtFixedRate, RandomAccessFile
          this.messageStore.start();
      }
  
      if (this.remotingServer != null) {
          // 使用Netty暴露Socket服务处理外部请求的调用
          this.remotingServer.start();
      }
  
      if (this.fastRemotingServer != null) {
          // 使用Netty暴露Socket服务处理外部请求的调用
          this.fastRemotingServer.start();
      }
  
      if (this.fileWatchService != null) {
          // 启动文件监听服务
          this.fileWatchService.start();
      }
  
      if (this.brokerOuterAPI != null) {
          // 启动 brokerOuterAPI 也就是 RemotingClient,使得 Broker 可以调用其它方
          this.brokerOuterAPI.start();
      }
  
      if (this.pullRequestHoldService != null) {
          // 启动 pullRequestHoldService 服务用于处理 Consumer 拉取消息
          this.pullRequestHoldService.start();
      }
  
      if (this.clientHousekeepingService != null) {
          // 启动 clientHousekeepingService 服务用于处理 Producer、Consumer、FilterServer 的存活
          this.clientHousekeepingService.start();
      }
  
      if (this.filterServerManager != null) {
          // 启动 filterServerManager 服务用于定时更新 FilterServer
          this.filterServerManager.start();
      }
  
      if (!messageStoreConfig.isEnableDLegerCommitLog()) {
          startProcessorByHa(messageStoreConfig.getBrokerRole());
          handleSlaveSynchronize(messageStoreConfig.getBrokerRole());
          // 注册 Broker 信息到 NameServer
          this.registerBrokerAll(true, false, true);
      }
  
      // 在注册完后,会创建定时任务发送心跳包
      this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
  
          @Override
          public void run() {
              try {
                  // 每10s向NameSrv发送心跳包,NameSrv会定时扫描broker列表,去掉长时间没发送心跳包的broker
                  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);
  
      if (this.brokerStatsManager != null) {
          // 启动 Broker 中的指标统计
          this.brokerStatsManager.start();
      }
  
      if (this.brokerFastFailure != null) {
          // 启动 Broker 请求列表的过期请求清除任务
          this.brokerFastFailure.start();
      }
  }

调用this.registerBrokerAll方法注册broker到NameSrv上,在其内部调用doRegisterBrokerAll方法。doRegisterBrokerAll方法内部调用this.brokerOuterAPI.registerBrokerAll方法封装请求头,然后遍历NameSrv列表,向每个NameSrv发起注册请求。

Broker启动时会向集群中所有NameServer发送心跳语句,每隔30s想集群中所有NameServer发送心跳包,NameServer收到心跳包时会更新brokerLiveTable缓存中BrokerLiveInfo的lastUpdateTimeStamp,然后NameServer每隔10s扫描brokerLiveTable,如果连续120s没有收到心跳包,NameServer将移除broker信息同时关闭Socket连接

  public synchronized void registerBrokerAll(final boolean checkOrderConfig, boolean oneway, boolean forceRegister) {
      TopicConfigSerializeWrapper topicConfigWrapper = this.getTopicConfigManager().buildTopicConfigSerializeWrapper();
  
      if (!PermName.isWriteable(this.getBrokerConfig().getBrokerPermission())
          || !PermName.isReadable(this.getBrokerConfig().getBrokerPermission())) {
          ConcurrentHashMap<String, TopicConfig> topicConfigTable = new ConcurrentHashMap<>();
          for (TopicConfig topicConfig : topicConfigWrapper.getTopicConfigTable().values()) {
              TopicConfig tmp =
                  new TopicConfig(topicConfig.getTopicName(), topicConfig.getReadQueueNums(), topicConfig.getWriteQueueNums(),
                                  this.brokerConfig.getBrokerPermission());
              topicConfigTable.put(topicConfig.getTopicName(), tmp);
          }
          topicConfigWrapper.setTopicConfigTable(topicConfigTable);
      }
  
      if (forceRegister || needRegister(this.brokerConfig.getBrokerClusterName(),
                                        this.getBrokerAddr(),
                                        this.brokerConfig.getBrokerName(),
                                        this.brokerConfig.getBrokerId(),
                                        this.brokerConfig.getRegisterBrokerTimeoutMills())) {
          doRegisterBrokerAll(checkOrderConfig, oneway, topicConfigWrapper);
      }
  }
  
  private void doRegisterBrokerAll(boolean checkOrderConfig, boolean oneway,
                                   TopicConfigSerializeWrapper topicConfigWrapper) {
      // 注册broker的信息到NameSrv上
      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());
  
      if (registerBrokerResultList.size() > 0) {
          RegisterBrokerResult registerBrokerResult = registerBrokerResultList.get(0);
          if (registerBrokerResult != null) {
              if (this.updateMasterHAServerAddrPeriodically && registerBrokerResult.getHaServerAddr() != null) {
                  this.messageStore.updateHaMasterAddress(registerBrokerResult.getHaServerAddr());
              }
  
              this.slaveSynchronize.setMasterAddr(registerBrokerResult.getMasterAddr());
  
              if (checkOrderConfig) {
                  this.getTopicConfigManager().updateOrderTopicConfig(registerBrokerResult.getKvTable());
              }
          }
      }
  }

进入this.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) {
  
      // 线程安全的List 适用于写操作少的场景,因为每次都要复制副本
      final List<RegisterBrokerResult> registerBrokerResultList = new CopyOnWriteArrayList<>();
      // 获取NameServerAddress列表
      List<String> nameServerAddressList = this.remotingClient.getNameServerAddressList();
      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);
          // 多线程批量发送请求,使用CountDownLatch同步返回
          final CountDownLatch countDownLatch = new CountDownLatch(nameServerAddressList.size());
          for (final String namesrvAddr : nameServerAddressList) {
              brokerOuterExecutor.execute(() -> {
                  try {
                      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;
  }

接下来就是发送网络请求的registerBroker方法,主要用到基于Netty封装的NettyRemotingClient,该方法设置请求的Code为REGISTER_BROKER(103)

然后NameSrv会接收到该注册消息,根据Code是REGISTER_BROKER(103) 调用org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#registerBroker方法将Broker信息保存起来,使用了读写锁。

NameServer存储信息

先看看NameServer存储了哪些路由信息,在RouteInfoManager类中:

  private final static long BROKER_CHANNEL_EXPIRED_TIME = 1000 * 60 * 2;
  private final ReadWriteLock lock = new ReentrantReadWriteLock();
  private final HashMap<String/* topic */, Map<String /* brokerName */ , 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;
  • topicQueueTable:Topic消息队列路由信息,消息发送时根据路由表进行负载均衡。
  • brokerAddrTable:Broker基础信息,包含brokerName、所属集群名字,主备Broker地址。
  • clusterAddrTable:Broker集群信息,存储集群中所有Broker名称。
  • brokerLiveTable:Broker状态信息。NameServer每次收到心跳包时会替换该信息。
  • filterServerTable:Broker上的FilterServer列表,用于类模式消息过滤。

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

类图如下:

image.png

topicQueueTable、brokerAddrTable运行时结构如下:

image.png

brokerLiveTable、clusterAddrTable运行时结构如下:

image.png

NameServer处理心跳包

RouteInfoManager#registerBroker方法。分为下面几步执行:

1. clusterAddrTable表维护:

  // 防止并发修改路由表
  this.lock.writeLock().lockInterruptibly();
  // 该broker集群如果不存在,则创建新的
  Set<String> brokerNames = this.clusterAddrTable.computeIfAbsent(clusterName, k -> new HashSet<>());
  brokerNames.add(brokerName);

2. brokerAddrTable表维护

首先从bokerAddrTable根据brokerName尝试获取broker信息,如果不存在,则新建BrokerData并放入到bookerAddrTable中,registerFirst设置为true,表示第一次注册,否则直接替换原来的,registerFirst设置为false,表示非第一次注册

 BrokerData brokerData = this.brokerAddrTable.get(brokerName);
 if (null == brokerData) {
     // 表示第一次注册
     registerFirst = true;
     brokerData = new BrokerData(clusterName, brokerName, new HashMap<>());
     this.brokerAddrTable.put(brokerName, brokerData);
 }
 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
 // 移除旧的broker映射关系
 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()) {
         log.debug("remove entry {} from brokerData", item);
         it.remove();
     }
 }
 
 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);
 }
 // 该broker之前是否注册过
 registerFirst = registerFirst || (null == oldAddr);

3. 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());
             }
         }
     }
 }

如果broker是Master,并且BrokerTopic的信息发生变化或者是初次注册,则需要创建或更新Topic路由信息,为默认Topic自动注册路由信息。

4. 更新brokerLiveTable,broker存活信息,是路由删除的重要依据

 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);
 }

5.注册Broker的过滤器Server地址列表

 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上会关联多个FilterServer消息过滤器。如果此Broker为从节点,则需要查找该Broker的Master节点信息,并更新对应的masterAddr属性。

设计亮点

NameServer每收到一个心跳包,都会更细上述表的信息。上面源码更新各种表信息时,使用了锁粒度较小的读写锁,允许多个消息发送者并发读,保证消息高并发。但同一时刻NameServer只处理一个Broker心跳包,多个心跳包请求穿行执行。这是读写锁经典使用场景

2.2 路由删除

NameServer会每隔10s来扫描brokerLiveTable状态表,如果BrokerLive的lastUpdateTimestamp的时间戳距当前时间超过120s,则认为Broker失效,移除该Broker,同时更新topicQueueTable、brokerAddrTable、brokerLiveTable、filterServerTable

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

  1. NameServer定时扫描brokerLiveTable检测上次心跳包与当前系统时间的时间差,如果时间戳大于120s,则需要移除该Broker信息
  2. Broker在正常被关闭的情况下,会执行unregisterBroker指令

下面介绍第一种方式:

 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);
             this.onChannelDestroy(next.getKey(), next.getValue().getChannel());
 ​
             removeCount++;
         }
     }
 ​
     return removeCount;
 }

RouteInfoManager#onChannelDestroy方法核心处理:关闭channel,删除与该broker相关的路由信息。

第一步:申请写锁,将brokerAddress从brokerLiveTable表和filterServerTable表中移除

 this.lock.writeLock().lockInterruptibly();
 this.brokerLiveTable.remove(brokerAddrFound);
 this.filterServerTable.remove(brokerAddrFound);

第二步:维护brokerAddrTable。从brokerData中的brokerAddr中找到具体的broker,从BrokerData中移除。最后如果移除后BrokerData中不再包含其他Broker,则从brokerAddrTable中移除该brokerName对应条目。

 String brokerNameFound = null;
 boolean removeBrokerName = false;
 Iterator<Entry<String, BrokerData>> itBrokerAddrTable =
     this.brokerAddrTable.entrySet().iterator();
 while (itBrokerAddrTable.hasNext() && (null == brokerNameFound)) {
     BrokerData brokerData = itBrokerAddrTable.next().getValue();
 ​
     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();
         if (brokerAddr.equals(brokerAddrFound)) {
             brokerNameFound = brokerData.getBrokerName();
             it.remove();
             log.info("remove brokerAddr[{}, {}] from brokerAddrTable, because channel destroyed",
                      brokerId, brokerAddr);
             break;
         }
     }
 ​
     if (brokerData.getBrokerAddrs().isEmpty()) {
         removeBrokerName = true;
         itBrokerAddrTable.remove();
         log.info("remove brokerName[{}] from brokerAddrTable, because channel destroyed",
                  brokerData.getBrokerName());
     }
 }

第三步:根据brokerName,从clusterAddrTable表中找到Broker并从集群中移除。移除后集群(brokerNames)中不包含任何Broker,则将该集群从clusterAddrTable中移除

 if (brokerNameFound != null && removeBrokerName) {
     Iterator<Entry<String, Set<String>>> it = this.clusterAddrTable.entrySet().iterator();
     while (it.hasNext()) {
         Entry<String, Set<String>> entry = it.next();
         String clusterName = entry.getKey();
         Set<String> brokerNames = entry.getValue();
         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);
                 it.remove();
             }
 ​
             break;
         }
     }
 }

第四步:根据brokerName,遍历所有主题队列,如果队列中包含了当前Broker的队列,则移除。如果topic只包含待移除Broker的队列,从路由表中删除该topic.

 if (removeBrokerName) {
     String finalBrokerNameFound = brokerNameFound;
     Set<String> needRemoveTopic = new HashSet<>();
 ​
     topicQueueTable.forEach((topic, queueDataMap) -> {
         QueueData old = queueDataMap.remove(finalBrokerNameFound);
         log.info("remove topic[{} {}], from topicQueueTable, because channel destroyed",
                  topic, old);
 ​
         if (queueDataMap.size() == 0) {
             log.info("remove topic[{}] all queue, from topicQueueTable, because channel destroyed",
                      topic);
             needRemoveTopic.add(topic);
         }
     });
 ​
     needRemoveTopic.forEach(topicQueueTable::remove);
 }

第五步:释放锁

 this.lock.writeLock().unlock();

2.3 路由发现

启动一个生产者很简单,代码如下:

  DefaultMQProducer producer = new DefaultMQProducer("Producer");
  producer.setNamesrvAddr("127.0.0.1:9876");
  producer.start();

上面先告知Producer NameSrv 的地址,紧接着调用了start启动生产者。

image.png

下面会执行到org.apache.rocketmq.client.impl.factory.MQClientInstance#startScheduledTask方法,该方法也启动了一些任务:

 private void startScheduledTask() {
     ......
 ​
     // 这里主要看这个方法
     this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
 ​
         @Override
         public void run() {
             try {
                 // 更新Topic的路由信息
                 MQClientInstance.this.updateTopicRouteInfoFromNameServer();
             } catch (Exception e) {
                 log.error("ScheduledTask updateTopicRouteInfoFromNameServer exception", e);
             }
         }
     }, 10, this.clientConfig.getPollNameServerInterval(), TimeUnit.MILLISECONDS);
 ​
     ......
 }

主要看updateTopicRouteInfoFromNameServer这个任务:

     public void updateTopicRouteInfoFromNameServer() {
         Set<String> topicList = new HashSet<String>();
 ​
         // Consumer
         {
             Iterator<Entry<String, MQConsumerInner>> it = this.consumerTable.entrySet().iterator();
             while (it.hasNext()) {
                 Entry<String, MQConsumerInner> entry = it.next();
                 MQConsumerInner impl = entry.getValue();
                 if (impl != null) {
                     Set<SubscriptionData> subList = impl.subscriptions();
                     if (subList != null) {
                         for (SubscriptionData subData : subList) {
                             topicList.add(subData.getTopic());
                         }
                     }
                 }
             }
         }
 ​
         // Producer
         {
             Iterator<Entry<String, MQProducerInner>> it = this.producerTable.entrySet().iterator();
             while (it.hasNext()) {
                 Entry<String, MQProducerInner> entry = it.next();
                 MQProducerInner impl = entry.getValue();
                 if (impl != null) {
                     Set<String> lst = impl.getPublishTopicList();
                     topicList.addAll(lst);
                 }
             }
         }
 ​
         for (String topic : topicList) {
             this.updateTopicRouteInfoFromNameServer(topic);
         }
     }

从生产者和消费者收集Topic信息,然后遍历Topic列表,调用this.updateTopicRouteInfoFromNameServer(topic) 方法获取每个Topic的路由信息,保存到TopicRouteData中,包含Topic对应的Broker和Queue。然后将Brooker信息保存到brokerAddrTable表中。

 public class TopicRouteData extends RemotingSerializable {
     private String orderTopicConf;
     private List<QueueData> queueDatas;
     private List<BrokerData> brokerDatas;
     private HashMap<String/* brokerAddr */, List<String>/* Filter Server */> filterServerTable;
     ......
 }

到这里,生产者就成功从NameSrv获取到了Broker信息

小结

image.png

知识点

  • ReentrantReadWriteLock读写锁使用
  • CopyOnWriteArrayList
  • CountDownLatch
  • HashMap的computeIfAbsent方法
  • scheduleAtFixedRate
  • RandomAccessFile
  • Runtime.getRuntime().addShutdownHook 关闭线程池