1. NameServer 解决什么问题

为避免消息服务器因单点故障导致整个系统瘫痪,通常会部署多台消息服务器共同承担消息的存储,那么就会存在几个问题,生产者如何知道将消息发送给哪台消息服务器呢?某台消息服务器重启宕机了,生产者如何在不重启服务器的情况下感知呢?所以RocketMQ 引入NameServer 来解决这些问题

2. NameServer 架构设计

NameServer 路由中心 - 图1

Broker 消息服务器在启动时会向所有NameServer 注册,生产者在发送消息之前会先从NameServer获取Broker 服务器的地址列表,然后根据负载算法从列表中选择一台消息服务器发送消息。NameServer与每台Broker服务器保持长连接,并间隔10s检测Broker是否存活,如果检测道Broker宕机会从NameServer中的注册表中删除该节点,但是路由变化不会立刻通知生产者,这是为了降低NameServer 实现的复杂性,因此需要在消息发送端提供容错机制来保证消息发送的高可用性。

NameServer 本身高可用性可通过部署多台NameServer服务器来实现,但彼此之间互不通信。这样虽然在某一个时刻数据并不会完全相同,但对消息发送不会造成重大影响,无非就是短暂造成消息发送不均衡,这也是RocketMQ NameServer 设计的一个亮点。

消息客户端和NameServer、Broker 的交互设计特点如下:

  1. Broker 每个30s向NameServer集群的每一台机器发送心跳包,包含自身创建的topic路由等信息。
  2. 消息客户端每个30s向NameServer更新对应的topic的路由信息
  3. NameServer 收到Broker 发送的心跳包,会记录时间戳放到brokerLiveTable中,然后每隔10s扫描一次brokerLiveTable,如果在120s以内没有收到心跳包就认为Broker失效,更新topic路由信息,将失效的Broker信息移除

3. NameServer 启动

3.1. 时序图

NameServer 路由中心 - 图2

3.2. 源码分析

NameServer的启动入口类是org.apache.rocketmq.namesrv.NamesrvStartup

  1. public class NamesrvStartup {
  2. public static NamesrvController main0(String[] args) {
  3. try {
  4. // 创建NamesrvController
  5. NamesrvController controller = createNamesrvController(args);
  6. // 启动NamesrvController
  7. start(controller);
  8. String tip = "The Name Server boot success. serializeType=" + RemotingCommand.getSerializeTypeConfigInThisServer();
  9. log.info(tip);
  10. System.out.printf("%s%n", tip);
  11. return controller;
  12. } catch (Throwable e) {
  13. e.printStackTrace();
  14. System.exit(-1);
  15. }
  16. return null;
  17. }
  18. }

3.2.1. 创建NamesrvController

org.apache.rocketmq.namesrv.NamesrvStartup.createNamesrvController() 方法创建NamesrvController

  1. public static NamesrvController createNamesrvController(String[] args) throws IOException, JoranException {
  2. // 设置版本号为当前版本号
  3. System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, Integer.toString(MQVersion.CURRENT_VERSION));
  4. //PackageConflictDetect.detectFastjson();
  5. //构造org.apache.commons.cli.Options,并添加-h -n参数,-h参数是打印帮助信息,-n参数是指定namesrvAddr
  6. Options options = ServerUtil.buildCommandlineOptions(new Options());
  7. //初始化commandLine,并在options中添加-c -p参数,-c指定nameserver的配置文件路径,-p标识打印配置信息
  8. commandLine = ServerUtil.parseCmdLine("mqnamesrv", args, buildCommandlineOptions(options), new PosixParser());
  9. if (null == commandLine) {
  10. System.exit(-1);
  11. return null;
  12. }
  13. // 创建NamesrvConfig 对象,包含业务参数
  14. final NamesrvConfig namesrvConfig = new NamesrvConfig();
  15. // 创建NettyServerConfig 对象,包含网络参数
  16. final NettyServerConfig nettyServerConfig = new NettyServerConfig();
  17. // 指定NamesrvServer 端口,默认9876
  18. nettyServerConfig.setListenPort(9876);
  19. if (commandLine.hasOption('c')) {
  20. String file = commandLine.getOptionValue('c');
  21. if (file != null) {
  22. InputStream in = new BufferedInputStream(new FileInputStream(file));
  23. properties = new Properties();
  24. properties.load(in);
  25. MixAll.properties2Object(properties, namesrvConfig);
  26. MixAll.properties2Object(properties, nettyServerConfig);
  27. // 配置文件路径
  28. namesrvConfig.setConfigStorePath(file);
  29. System.out.printf("load config properties file OK, %s%n", file);
  30. in.close();
  31. }
  32. }
  33. //命令行带有-p,说明是打印参数的命令,那么就打印出NamesrvConfig和NettyServerConfig的属性
  34. // 在启动NameServer时可以先使用./mqnameserver -c configFile -p打印当前加载的配置属性
  35. if (commandLine.hasOption('p')) {
  36. InternalLogger console = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_CONSOLE_NAME);
  37. MixAll.printObjectProperties(console, namesrvConfig);
  38. MixAll.printObjectProperties(console, nettyServerConfig);
  39. //打印参数命令不需要启动nameserver服务,只需要打印参数即可
  40. System.exit(0);
  41. }
  42. //解析命令行参数,并加载到namesrvConfig中
  43. MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), namesrvConfig);
  44. //检查ROCKETMQ_HOME,不能为空
  45. if (null == namesrvConfig.getRocketmqHome()) {
  46. System.out.printf("Please set the %s variable in your environment to match the location of the RocketMQ installation%n", MixAll.ROCKETMQ_HOME_ENV);
  47. System.exit(-2);
  48. }
  49. //初始化logback日志工厂,rocketmq默认使用logback作为日志输出
  50. LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
  51. JoranConfigurator configurator = new JoranConfigurator();
  52. configurator.setContext(lc);
  53. lc.reset();
  54. configurator.doConfigure(namesrvConfig.getRocketmqHome() + "/conf/logback_namesrv.xml");
  55. log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);
  56. MixAll.printObjectProperties(log, namesrvConfig);
  57. MixAll.printObjectProperties(log, nettyServerConfig);
  58. // 根据namesrvConfig和nettyServerConfig 创建NamesrvController 对象
  59. final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig);
  60. // remember all configs to prevent discard
  61. // 合并配置文件
  62. controller.getConfiguration().registerConfig(properties);
  63. return controller;
  64. }

创建NamesrvController主要分为两步

第一步:通过命令行中获取配置。赋值给NamesrvConfig和NettyServerConfig类 第二步:根据配置类NamesrvConfig和NettyServerConfig构造一个NamesrvController实例

NamesrvConfig 是NameServer的配置信息,NeetyServerConfig是NameServer的网络配置信息

NamesrvConfig的配置信息

  1. public class NamesrvConfig {
  2. private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);
  3. // RocketMQ 主目录,通过-Drocketmq.home.dir=path 或设置环境变量ROCKETMQ_HOME 可以配置RocketMQ的主目录
  4. private String rocketmqHome = System.getProperty(MixAll.ROCKETMQ_HOME_PROPERTY, System.getenv(MixAll.ROCKETMQ_HOME_ENV));
  5. // NameServer存储KV配置属性的持久化路径
  6. private String kvConfigPath = System.getProperty("user.home") + File.separator + "namesrv" + File.separator + "kvConfig.json";
  7. // NameServer 默认配置文件路径,NameServer 启动时如果要通过配置文件配置NameServer启动属性,使用-c 选项
  8. private String configStorePath = System.getProperty("user.home") + File.separator + "namesrv" + File.separator + "namesrv.properties";
  9. private String productEnvName = "center";
  10. private boolean clusterTest = false;
  11. // 支持顺序消息,默认不支持
  12. private boolean orderMessageEnable = false;

NettyServerConfig的配置信息

  1. public class NettyServerConfig implements Cloneable {
  2. // 默认监听端口,在启动时会被初始化为9876
  3. private int listenPort = 8888;
  4. // Netty 业务线程池线程个数
  5. private int serverWorkerThreads = 8;
  6. // Netty public 任务线程池线程个数,Netty 网络会根据业务类型创建不同的线程池,比如处理消息发送、消息消费、心跳检测等。如果该业务类型(RequestCode)未注册线程池,则由public 线程池执行
  7. private int serverCallbackExecutorThreads = 0;
  8. // I/O线程池个数,主要是NameServer、Broker端解析请求、返回相应线程个数。这类线程主要用于处理网络请求,先解析请求包,然后转发到各业务线程池完成具体的业务操作,最后将结果返回给调用方
  9. private int serverSelectorThreads = 3;
  10. // send oneway 消息请求的并发度(Broker 端参数)
  11. private int serverOnewaySemaphoreValue = 256;
  12. private int serverAsyncSemaphoreValue = 64;
  13. // 网络链接最大空闲时间,默认为120s,如果链接空闲时间超过该参数设置的值,链接将被关闭
  14. private int serverChannelMaxIdleTimeSeconds = 120;
  15. // 网络socket 发送缓存区大小,默认为64KB
  16. private int serverSocketSndBufSize = NettySystemConfig.socketSndbufSize;
  17. // 网络socket 接收缓存区大小,默认为64KB
  18. private int serverSocketRcvBufSize = NettySystemConfig.socketRcvbufSize;
  19. private int writeBufferHighWaterMark = NettySystemConfig.writeBufferHighWaterMark;
  20. private int writeBufferLowWaterMark = NettySystemConfig.writeBufferLowWaterMark;
  21. private int serverSocketBacklog = NettySystemConfig.socketBacklog;
  22. // Buffer 是否开启缓存,默认开启
  23. private boolean serverPooledByteBufAllocatorEnable = true;
  24. /**
  25. * make make install
  26. *
  27. *
  28. * ../glibc-2.10.1/configure \ --prefix=/usr \ --with-headers=/usr/include \
  29. * --host=x86_64-linux-gnu \ --build=x86_64-pc-linux-gnu \ --without-gd
  30. */
  31. // 是否启用Epool I/O模型,Linux 环境下建议开启
  32. private boolean useEpollNativeSelector = false;
  33. // 省略Set/Get
  34. }

3.2.2. 初始化NamesrvController

创建好NamesrvController对象后,接下来就是对它进行初始化。org.apache.rocketmq.namesrv.initialize()初始化NamesrvController

  1. public boolean initialize() {
  2. // 加载KV配置,创建NettyServer 网络处理对象
  3. this.kvConfigManager.load();
  4. // 根据nettyServerConfig初始化一个netty服务器
  5. //brokerHousekeepingService是在NamesrvController实例化时构造函数里实例化的,该类负责Broker连接事件的处理,实现了ChannelEventListener,主要用来管理RouteInfoManager的brokerLiveTable
  6. this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);
  7. //初始化负责处理Netty网络交互数据的线程池,默认线程数是8个
  8. this.remotingExecutor =
  9. Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), new ThreadFactoryImpl("RemotingExecutorThread_"));
  10. //注册Netty服务端业务处理逻辑,如果开启了clusterTest,那么注册的请求处理类是ClusterTestRequestProcessor,否则请求处理类是DefaultRequestProcessor
  11. this.registerProcessor();
  12. // 定时任务(心跳检测)每隔10s扫描一次Broker,移除处于未激活状态的Broker
  13. this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
  14. @Override
  15. public void run() {
  16. NamesrvController.this.routeInfoManager.scanNotActiveBroker();
  17. }
  18. }, 5, 10, TimeUnit.SECONDS);
  19. // 定时任务(心跳检测)每隔10min 打印一次KV 配置
  20. this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
  21. @Override
  22. public void run() {
  23. NamesrvController.this.kvConfigManager.printAllPeriodically();
  24. }
  25. }, 1, 10, TimeUnit.MINUTES);
  26. if (TlsSystemConfig.tlsMode != TlsMode.DISABLED) {
  27. // Register a listener to reload SslContext
  28. try {
  29. fileWatchService = new FileWatchService(
  30. new String[] {
  31. TlsSystemConfig.tlsServerCertPath,
  32. TlsSystemConfig.tlsServerKeyPath,
  33. TlsSystemConfig.tlsServerTrustCertPath
  34. },
  35. new FileWatchService.Listener() {
  36. boolean certChanged, keyChanged = false;
  37. @Override
  38. public void onChanged(String path) {
  39. if (path.equals(TlsSystemConfig.tlsServerTrustCertPath)) {
  40. log.info("The trust certificate changed, reload the ssl context");
  41. reloadServerSslContext();
  42. }
  43. if (path.equals(TlsSystemConfig.tlsServerCertPath)) {
  44. certChanged = true;
  45. }
  46. if (path.equals(TlsSystemConfig.tlsServerKeyPath)) {
  47. keyChanged = true;
  48. }
  49. if (certChanged && keyChanged) {
  50. log.info("The certificate and private key changed, reload the ssl context");
  51. certChanged = keyChanged = false;
  52. reloadServerSslContext();
  53. }
  54. }
  55. private void reloadServerSslContext() {
  56. ((NettyRemotingServer) remotingServer).loadSslContext();
  57. }
  58. });
  59. } catch (Exception e) {
  60. log.warn("FileWatchService created error, can't load the certificate dynamically");
  61. }
  62. }
  63. return true;
  64. }

4. 心跳检测(路由删除)

4.1. 时序图

NameServer 路由中心 - 图3

4.2. 源码分析

心跳检测的源码入口org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#scanNotActiveBroker();
每隔10s执行一次,遍历brokerLiveInfo路由表,HashMap结构。检测BrokerLiveInfo的LastUpdateTimestamp上次收到的心跳包时间,如果超过120s,就认为Broker不可用,然后从brokerLiveInfo路由表中移除该Broker。

  1. public void scanNotActiveBroker() {
  2. // 遍历brokerLiveTable,这是Broker 状态信息
  3. Iterator<Entry<String, BrokerLiveInfo>> it = this.brokerLiveTable.entrySet().iterator();
  4. // 遍历Broker 信息
  5. while (it.hasNext()) {
  6. Entry<String, BrokerLiveInfo> next = it.next();
  7. // Broker 最后一次注册的心跳时间
  8. long last = next.getValue().getLastUpdateTimestamp();
  9. // 最后一次注册时间大于120s,从brokerLiveTable 移除Broker
  10. if ((last + BROKER_CHANNEL_EXPIRED_TIME) < System.currentTimeMillis()) {
  11. RemotingUtil.closeChannel(next.getValue().getChannel());
  12. it.remove();
  13. log.warn("The broker channel expired, {} {}ms", next.getKey(), BROKER_CHANNEL_EXPIRED_TIME);
  14. this.onChannelDestroy(next.getKey(), next.getValue().getChannel());
  15. }
  16. }
  17. }

从brokerLiveTable 移除Broker后,调用onChannelDestroy()删除与Broker相关的路由信息

  1. public void onChannelDestroy(String remoteAddr, Channel channel) {
  2. String brokerAddrFound = null;
  3. if (channel != null) {
  4. try {
  5. try {
  6. // 加锁
  7. this.lock.readLock().lockInterruptibly();
  8. Iterator<Entry<String, BrokerLiveInfo>> itBrokerLiveTable =
  9. this.brokerLiveTable.entrySet().iterator();
  10. // 对比channel 是否一样
  11. while (itBrokerLiveTable.hasNext()) {
  12. Entry<String, BrokerLiveInfo> entry = itBrokerLiveTable.next();
  13. if (entry.getValue().getChannel() == channel) {
  14. brokerAddrFound = entry.getKey();
  15. break;
  16. }
  17. }
  18. } finally {
  19. this.lock.readLock().unlock();
  20. }
  21. } catch (Exception e) {
  22. log.error("onChannelDestroy Exception", e);
  23. }
  24. }
  25. if (null == brokerAddrFound) {
  26. brokerAddrFound = remoteAddr;
  27. } else {
  28. log.info("the broker's channel destroyed, {}, clean it's data structure at once", brokerAddrFound);
  29. }
  30. if (brokerAddrFound != null && brokerAddrFound.length() > 0) {
  31. try {
  32. try {
  33. // Step1:申请写锁,根据brokerLiveTable、filterServerTable中移除Broker相关信息
  34. this.lock.writeLock().lockInterruptibly();
  35. // 从brokerLiveTable删除Broker
  36. this.brokerLiveTable.remove(brokerAddrFound);
  37. // 从filterServerTable 删除Broker
  38. this.filterServerTable.remove(brokerAddrFound);
  39. // Broker名称
  40. String brokerNameFound = null;
  41. // Broker 删除标志位,默认没删除
  42. boolean removeBrokerName = false;
  43. // Step2:维护brokerAddrTable
  44. // 遍历brokerAddTable,从BrokerData的HashMap中找到具体的Broker,从BrokerData中将其移除。如果移除后BrokerData中不在包含其他Broker
  45. // 就在brokerAddrTable中移除该brokerName对应的条目
  46. Iterator<Entry<String, BrokerData>> itBrokerAddrTable =
  47. this.brokerAddrTable.entrySet().iterator();
  48. while (itBrokerAddrTable.hasNext() && (null == brokerNameFound)) {
  49. BrokerData brokerData = itBrokerAddrTable.next().getValue();
  50. Iterator<Entry<Long, String>> it = brokerData.getBrokerAddrs().entrySet().iterator();
  51. while (it.hasNext()) {
  52. Entry<Long, String> entry = it.next();
  53. Long brokerId = entry.getKey();
  54. String brokerAddr = entry.getValue();
  55. if (brokerAddr.equals(brokerAddrFound)) {
  56. brokerNameFound = brokerData.getBrokerName();
  57. it.remove();
  58. log.info("remove brokerAddr[{}, {}] from brokerAddrTable, because channel destroyed",
  59. brokerId, brokerAddr);
  60. break;
  61. }
  62. }
  63. if (brokerData.getBrokerAddrs().isEmpty()) {
  64. removeBrokerName = true;
  65. itBrokerAddrTable.remove();
  66. log.info("remove brokerName[{}] from brokerAddrTable, because channel destroyed",
  67. brokerData.getBrokerName());
  68. }
  69. }
  70. // Step3:根据BrokerName从clusterAddrTable中找到Broker并将其从集群中移除
  71. // 如果移除后,集群中不包含任何Broker,就将该集群中clusterAddrTable中移除
  72. if (brokerNameFound != null && removeBrokerName) {
  73. // 遍历Broker集群
  74. Iterator<Entry<String, Set<String>>> it = this.clusterAddrTable.entrySet().iterator();
  75. while (it.hasNext()) {
  76. Entry<String, Set<String>> entry = it.next();
  77. String clusterName = entry.getKey();
  78. Set<String> brokerNames = entry.getValue();
  79. boolean removed = brokerNames.remove(brokerNameFound);
  80. if (removed) {
  81. log.info("remove brokerName[{}], clusterName[{}] from clusterAddrTable, because channel destroyed",
  82. brokerNameFound, clusterName);
  83. // 如果集群中已经不包含任何Broker,移除该集群
  84. if (brokerNames.isEmpty()) {
  85. log.info("remove the clusterName[{}] from clusterAddrTable, because channel destroyed and no broker in this cluster",
  86. clusterName);
  87. it.remove();
  88. }
  89. break;
  90. }
  91. }
  92. }
  93. // Step4:根据BrokerName,遍历所有主题队列,如果队列中包含当前Broker的队列,就移除
  94. // 如果topic中只包含待移除的Broker队列,就从路由表中删除该topic
  95. if (removeBrokerName) {
  96. // 遍历所有主题队列
  97. Iterator<Entry<String, List<QueueData>>> itTopicQueueTable =
  98. this.topicQueueTable.entrySet().iterator();
  99. while (itTopicQueueTable.hasNext()) {
  100. Entry<String, List<QueueData>> entry = itTopicQueueTable.next();
  101. String topic = entry.getKey();
  102. List<QueueData> queueDataList = entry.getValue();
  103. // 主题的所有队列信息
  104. Iterator<QueueData> itQueueData = queueDataList.iterator();
  105. while (itQueueData.hasNext()) {
  106. // 主题的某个队列信息
  107. QueueData queueData = itQueueData.next();
  108. // 队列所属Broker名称和被移除的Broker名称一样,就移除该队列
  109. if (queueData.getBrokerName().equals(brokerNameFound)) {
  110. itQueueData.remove();
  111. log.info("remove topic[{} {}], from topicQueueTable, because channel destroyed",
  112. topic, queueData);
  113. }
  114. }
  115. // 如果队列已经为空,就移除主题
  116. if (queueDataList.isEmpty()) {
  117. itTopicQueueTable.remove();
  118. log.info("remove topic[{}] all queue, from topicQueueTable, because channel destroyed",
  119. topic);
  120. }
  121. }
  122. }
  123. } finally {
  124. // Step5:释放锁
  125. this.lock.writeLock().unlock();
  126. }
  127. } catch (Exception e) {
  128. log.error("onChannelDestroy Exception", e);
  129. }
  130. }
  131. }

5. Broker 心跳注册(路由注册)

5.1. 时序图

NameServer 路由中心 - 图4

5.2. 路由元数据信息

Broker 会把自己的信息注册到Nameserver中,注册的信息就叫路由元数据信息,具体注册信息如下:

  1. // 1. Topic 消息队列的路由信息,消息发送时根据路由表进行负载均衡
  2. private final HashMap<String/* topic */, List<QueueData>> topicQueueTable;
  3. // 2. Broker 基础信息,包含brokerName、所属集群名称、主备Broker地址
  4. private final HashMap<String/* brokerName */, BrokerData> brokerAddrTable;
  5. // 3. Broker集群信息
  6. private final HashMap<String/* clusterName */, Set<String/* brokerName */>> clusterAddrTable;
  7. // 4. Broker 状态信息,NameServer 每次收到心跳包时会替换该信息
  8. private final HashMap<String/* brokerAddr */, BrokerLiveInfo> brokerLiveTable;
  9. // 5. Broker 上的FilterServer列表,用于类模式消息过滤
  10. private final HashMap<String/* brokerAddr */, List<String>/* Filter Server */> filterServerTable;
  1. topicQueueTable:topic 消息队列的路由信息,消息发送时根据路由表进行负债均衡
  2. brokerAddrTable:Broker基本信息,包含brokerName、所属集群名称、主备Broker地址
  3. clusterAddrTable:Broker集群信息,存储集群中所有Broker 的名称
  4. brokerLiveTable:Broker 状态信息,NameServer 每次收到心跳包(注册Broker信息)会替换该值
  5. filterServerTable:Broker上的FilterServer列表,用于类模式消息过滤。

    5.3. 源码分析

    5.3.1. Broker 发送心跳包(注册)

    Broker 会每隔10s向Nameserver 发送一次心跳,发送心跳的入口代码是:org.apache.rocketmq.broker#start()
    Broker启动后会创建一个线程,在程序启动10秒后执行,每隔30秒(默认30s,时间间隔在10秒到60秒之间,BrokerConfig.getRegisterNameServerPeriod()的默认值是30秒)执行一次

    1. // 向NameServer 发送心跳包,每隔10s发送一次
    2. this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    3. @Override
    4. public void run() {
    5. try {
    6. BrokerController.this.registerBrokerAll(true, false, brokerConfig.isForceRegister());
    7. } catch (Throwable e) {
    8. log.error("registerBrokerAll Exception", e);
    9. }
    10. }
    11. }, 1000 * 10, Math.max(10000, Math.min(brokerConfig.getRegisterNameServerPeriod(), 60000)), TimeUnit.MILLISECONDS);

    这里主要是组装一些心跳包检测的必要参数

    1. public synchronized void registerBrokerAll(final boolean checkOrderConfig, boolean oneway, boolean forceRegister) {
    2. // 构建Topic信息
    3. TopicConfigSerializeWrapper topicConfigWrapper = this.getTopicConfigManager().buildTopicConfigSerializeWrapper();
    4. if (!PermName.isWriteable(this.getBrokerConfig().getBrokerPermission())
    5. || !PermName.isReadable(this.getBrokerConfig().getBrokerPermission())) {
    6. ConcurrentHashMap<String, TopicConfig> topicConfigTable = new ConcurrentHashMap<String, TopicConfig>();
    7. for (TopicConfig topicConfig : topicConfigWrapper.getTopicConfigTable().values()) {
    8. TopicConfig tmp =
    9. new TopicConfig(topicConfig.getTopicName(), topicConfig.getReadQueueNums(), topicConfig.getWriteQueueNums(),
    10. this.brokerConfig.getBrokerPermission());
    11. topicConfigTable.put(topicConfig.getTopicName(), tmp);
    12. }
    13. topicConfigWrapper.setTopicConfigTable(topicConfigTable);
    14. }
    15. // 强制注册或者需要注册时,向NameServer 注册Broker信息
    16. if (forceRegister || needRegister(this.brokerConfig.getBrokerClusterName(),
    17. this.getBrokerAddr(),
    18. this.brokerConfig.getBrokerName(),
    19. this.brokerConfig.getBrokerId(),
    20. this.brokerConfig.getRegisterBrokerTimeoutMills())) {
    21. // 发送心跳包
    22. doRegisterBrokerAll(checkOrderConfig, oneway, topicConfigWrapper);
    23. }
    24. }

    心跳包的发送

    1. /**
    2. * 向NameServer 注册Broker
    3. * @param checkOrderConfig
    4. * @param oneway
    5. * @param topicConfigWrapper
    6. */
    7. private void doRegisterBrokerAll(boolean checkOrderConfig, boolean oneway,
    8. TopicConfigSerializeWrapper topicConfigWrapper) {
    9. // 发送心跳包
    10. List<RegisterBrokerResult> registerBrokerResultList = this.brokerOuterAPI.registerBrokerAll(
    11. this.brokerConfig.getBrokerClusterName(),
    12. this.getBrokerAddr(),
    13. this.brokerConfig.getBrokerName(),
    14. this.brokerConfig.getBrokerId(),
    15. this.getHAServerAddr(),
    16. topicConfigWrapper,
    17. this.filterServerManager.buildNewFilterServerList(),
    18. oneway,
    19. this.brokerConfig.getRegisterBrokerTimeoutMills(),
    20. this.brokerConfig.isCompressedRegister());
    21. if (registerBrokerResultList.size() > 0) {
    22. RegisterBrokerResult registerBrokerResult = registerBrokerResultList.get(0);
    23. if (registerBrokerResult != null) {
    24. if (this.updateMasterHAServerAddrPeriodically && registerBrokerResult.getHaServerAddr() != null) {
    25. this.messageStore.updateHaMasterAddress(registerBrokerResult.getHaServerAddr());
    26. }
    27. this.slaveSynchronize.setMasterAddr(registerBrokerResult.getMasterAddr());
    28. if (checkOrderConfig) {
    29. this.getTopicConfigManager().updateOrderTopicConfig(registerBrokerResult.getKvTable());
    30. }
    31. }
    32. }
    33. }

    Broker 心跳包的发送最终由org.apache.rocketmq.broker.out.BrokerOuterAPI#registerBrokerAll()方法实现发送,遍历所有Nameserver,依次向每台Nameserver发送心跳
    发送心跳包的具体逻辑:

  6. brokerAddr: broker地址

  7. brokerId:brokerId=0表示主节点,brokerId>0表示从节点
  8. brokerName:broker 名称
  9. clusterName :集群名称
  10. haServerAddr:主节点地址,初次请求时该值为空,从节点向NameServer注册后返回
  11. requestBody 心跳包参数对象,包含topicConfigSerializeWrapper属性,这个属性内部封装的是TopicConfigManager中的topicConfigTable属性,是一个HashMap,topicConfigTable属性存储的是Broker启动时默认的一些topic
  12. filterServerList:消息过滤服务器列表

    1. /**
    2. * 发送心跳包
    3. * @param clusterName 集群名称
    4. * @param brokerAddr broker地址
    5. * @param brokerName broker名称
    6. * @param brokerId brokerId=0表示主节点,brokerId>0表示从节点
    7. * @param haServerAddr 主节点地址,初次请求时该值为空,从节点向NameServer注册后返回
    8. * @param topicConfigWrapper 主题配置,内部封装的是TopicConfigManager中的topicConfigTable,内部存储的是Broker启动时默认的一些topic
    9. * 如:MinA11.SELF_TEST_TOPIC、MixA11.DEFAULT_TOPIC(AutoCreateTopicEnable=true
    10. * @param filterServerList 消息过滤服务列表
    11. * @param oneway
    12. * @param timeoutMills
    13. * @param compressed
    14. * @return
    15. */
    16. public List<RegisterBrokerResult> registerBrokerAll(
    17. final String clusterName,
    18. final String brokerAddr,
    19. final String brokerName,
    20. final long brokerId,
    21. final String haServerAddr,
    22. final TopicConfigSerializeWrapper topicConfigWrapper,
    23. final List<String> filterServerList,
    24. final boolean oneway,
    25. final int timeoutMills,
    26. final boolean compressed) {
    27. // 向NameServer发送心跳包的结果结合
    28. final List<RegisterBrokerResult> registerBrokerResultList = new CopyOnWriteArrayList<>();
    29. // 获取所有NameServer列表
    30. List<String> nameServerAddressList = this.remotingClient.getNameServerAddressList();
    31. if (nameServerAddressList != null && nameServerAddressList.size() > 0) {
    32. final RegisterBrokerRequestHeader requestHeader = new RegisterBrokerRequestHeader();
    33. requestHeader.setBrokerAddr(brokerAddr);
    34. requestHeader.setBrokerId(brokerId);
    35. requestHeader.setBrokerName(brokerName);
    36. requestHeader.setClusterName(clusterName);
    37. requestHeader.setHaServerAddr(haServerAddr);
    38. requestHeader.setCompressed(compressed);
    39. // 向NameServer 发送心跳包的参数
    40. RegisterBrokerBody requestBody = new RegisterBrokerBody();
    41. //主题配置,内部封装的是TopicConfigManager中的topicConfigTable,内部存储的是Broker启动时默认的一些topic
    42. // 如:MinA11.SELF_TEST_TOPIC、MixA11.DEFAULT_TOPIC(AutoCreateTopicEnable=true
    43. requestBody.setTopicConfigSerializeWrapper(topicConfigWrapper);
    44. // 消息过滤服务器列表
    45. requestBody.setFilterServerList(filterServerList);
    46. final byte[] body = requestBody.encode(compressed);
    47. final int bodyCrc32 = UtilAll.crc32(body);
    48. requestHeader.setBodyCrc32(bodyCrc32);
    49. final CountDownLatch countDownLatch = new CountDownLatch(nameServerAddressList.size());
    50. // 遍历所有NameServer,依次向每个NameServer发送心跳包
    51. for (final String namesrvAddr : nameServerAddressList) {
    52. brokerOuterExecutor.execute(new Runnable() {
    53. @Override
    54. public void run() {
    55. try {
    56. // 向NameServer发送心跳,返回心跳发送结果
    57. RegisterBrokerResult result = registerBroker(namesrvAddr, oneway, timeoutMills, requestHeader, body);
    58. // 将发送结果放到registerBrokerResultList 集合
    59. if (result != null) {
    60. registerBrokerResultList.add(result);
    61. }
    62. log.info("register broker[{}]to name server {} OK", brokerId, namesrvAddr);
    63. } catch (Exception e) {
    64. log.warn("registerBroker Exception, {}", namesrvAddr, e);
    65. } finally {
    66. countDownLatch.countDown();
    67. }
    68. }
    69. });
    70. }
    71. try {
    72. // 等待Broker 注冊,如果注册超时抛异常
    73. countDownLatch.await(timeoutMills, TimeUnit.MILLISECONDS);
    74. } catch (InterruptedException e) {
    75. }
    76. }
    77. return registerBrokerResultList;
    78. }

    路由注册最终通过registerBroker()方法实现

    1. private RegisterBrokerResult registerBroker(
    2. final String namesrvAddr,
    3. final boolean oneway,
    4. final int timeoutMills,
    5. final RegisterBrokerRequestHeader requestHeader,
    6. final byte[] body
    7. ) throws RemotingCommandException, MQBrokerException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException,
    8. InterruptedException {
    9. // 注册Broker 的Netty请求对象,Broker 注册的requestCode=RequestCode.REGISTER_BROKER
    10. // Nameserver 端的接收请求后,会根据requestCode 转发给不同的业务处理
    11. RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.REGISTER_BROKER, requestHeader);
    12. request.setBody(body);
    13. if (oneway) {
    14. try {
    15. // 单向调用,没有返回值,不返回nameserver 返回结果
    16. this.remotingClient.invokeOneway(namesrvAddr, request, timeoutMills);
    17. } catch (RemotingTooMuchRequestException e) {
    18. // Ignore
    19. }
    20. return null;
    21. }
    22. // 异步调用向nameserver注册Broker,会有返回值
    23. RemotingCommand response = this.remotingClient.invokeSync(namesrvAddr, request, timeoutMills);
    24. assert response != null;
    25. switch (response.getCode()) {
    26. case ResponseCode.SUCCESS: {
    27. // 注册成功后,获取返回的responseHeader
    28. RegisterBrokerResponseHeader responseHeader =
    29. (RegisterBrokerResponseHeader) response.decodeCommandCustomHeader(RegisterBrokerResponseHeader.class);
    30. // 封装返回结果,更新masterAddr和haServerAddr
    31. RegisterBrokerResult result = new RegisterBrokerResult();
    32. result.setMasterAddr(responseHeader.getMasterAddr());
    33. result.setHaServerAddr(responseHeader.getHaServerAddr());
    34. if (response.getBody() != null) {
    35. result.setKvTable(KVTable.decode(response.getBody(), KVTable.class));
    36. }
    37. return result;
    38. }
    39. default:
    40. break;
    41. }
    42. throw new MQBrokerException(response.getCode(), response.getRemark(), requestHeader == null ? null : requestHeader.getBrokerAddr());
    43. }

    5.3.2. NameServer 处理心跳包(注册)

    在Broker 发送心跳包后,Nameserver负责接收,并处理心跳。Broker发出路由注册的心跳包之后,NameServer会根据心跳包中的requestCode进行处理。NameServer的默认网络处理器是org.apache.rocketmq.namesrv.processor.DefaultRequestProcessor类,Broker向NameServer通过Netty发送心跳时,会发送requestCode=RequestCode._REGISTER_BROKER,接着会将心跳注册请求转发给_registerBroker()方法处理

    1. /**
    2. * Netty 接收请求处理
    3. * @param ctx
    4. * @param request
    5. * @return
    6. * @throws RemotingCommandException
    7. */
    8. @Override
    9. public RemotingCommand processRequest(ChannelHandlerContext ctx,
    10. // ...省略代码
    11. switch (request.getCode()) {
    12. // ...省略代码
    13. case RequestCode.REGISTER_BROKER:
    14. Version brokerVersion = MQVersion.value2Version(request.getVersion());
    15. if (brokerVersion.ordinal() >= MQVersion.Version.V3_0_11.ordinal()) {
    16. return this.registerBrokerWithFilterServer(ctx, request);
    17. } else {
    18. // 向NameServer 注册Broker
    19. return this.registerBroker(ctx, request);
    20. }
    21. //... 省略代码
    22. }
    23. return null;
    24. }

Nameserver 接收Broker心跳包最终由org.apache.rocketmq.namesrv.routeinfo.registerBroker()处理

  1. /**
  2. * 向NameServer注册Broker 信息
  3. * @param clusterName Broker 集群名称
  4. * @param brokerAddr Broker 地址
  5. * @param brokerName Broker 名称
  6. * @param brokerId BrokerId 大于0为从节点,0表示主节点
  7. * @param haServerAddr 主节点地址,初次请求时为空,从节点向Nameserver注册后返回
  8. * @param topicConfigWrapper Topic 配置信息
  9. * @param filterServerList 消息过滤服务器列表
  10. * @param channel Netty 通道对象,知道是哪个Netty客户端
  11. * @return
  12. */
  13. public RegisterBrokerResult registerBroker(
  14. final String clusterName,
  15. final String brokerAddr,
  16. final String brokerName,
  17. final long brokerId,
  18. final String haServerAddr,
  19. final TopicConfigSerializeWrapper topicConfigWrapper,
  20. final List<String> filterServerList,
  21. final Channel channel) {
  22. RegisterBrokerResult result = new RegisterBrokerResult();
  23. try {
  24. try {
  25. // Step1:路由注册加写锁,防止并发修改RouteInfoManager中路由表
  26. this.lock.writeLock().lockInterruptibly();
  27. // 判断Broker 所属集群是否存在,不存在则创建集群,然后将Broker名加入集群Broker集合
  28. Set<String> brokerNames = this.clusterAddrTable.get(clusterName);
  29. if (null == brokerNames) {
  30. brokerNames = new HashSet<String>();
  31. // 将Broker 加入 clusterAddrTable 集群集合
  32. this.clusterAddrTable.put(clusterName, brokerNames);
  33. }
  34. brokerNames.add(brokerName);
  35. // 是否第一次注册Broker信息,默认不是第一次
  36. boolean registerFirst = false;
  37. // Step2:维护BrokerData信息,尝试从brokerAddrTable 获取Broker信息
  38. // 首先从brokerAddrTable中根据broker名称获取Broker信息,如果不存在就新建BrokerData并放入brokerAddrTable
  39. BrokerData brokerData = this.brokerAddrTable.get(brokerName);
  40. // Broker信息不存在
  41. if (null == brokerData) {
  42. // 设置Broker信息已经被注册,true表示第一次注册
  43. registerFirst = true;
  44. // 创建BrokerData信息
  45. brokerData = new BrokerData(clusterName, brokerName, new HashMap<Long, String>());
  46. // BrokerData信息加入brokerAddrTable
  47. this.brokerAddrTable.put(brokerName, brokerData);
  48. }
  49. Map<Long, String> brokerAddrsMap = brokerData.getBrokerAddrs();
  50. //Switch slave to master: first remove <1, IP:PORT> in namesrv, then add <0, IP:PORT>
  51. //The same IP:PORT must only have one record in brokerAddrTable
  52. Iterator<Entry<Long, String>> it = brokerAddrsMap.entrySet().iterator();
  53. while (it.hasNext()) {
  54. Entry<Long, String> item = it.next();
  55. if (null != brokerAddr && brokerAddr.equals(item.getValue()) && brokerId != item.getKey()) {
  56. it.remove();
  57. }
  58. }
  59. String oldAddr = brokerData.getBrokerAddrs().put(brokerId, brokerAddr);
  60. registerFirst = registerFirst || (null == oldAddr);
  61. // Step3:如果Broker 为主节点,并且Broker的topic配置信息发生变化或者是初次注册
  62. // 需要创建或者更新topic路由元数据,填充topicQueueTabe,如果是默认主题自动注册路由信息,其中包含
  63. // MixAll.DEFAULTOPIC的路由信息。当消息生产者发送主题时,如果该主题没有创建,并且BrokerConfig的autoCreateTopicEnable=true
  64. // 那么就返回MixAll_DEFAULT_TOPIC的路由信息
  65. if (null != topicConfigWrapper
  66. && MixAll.MASTER_ID == brokerId) {
  67. // Broker的topic配置信息发生变化或者初次注册Broker
  68. // 创建或者更新topic路由元信息
  69. // 校验版本,决定是否需要创建或者更新topic路由元信息
  70. if (this.isBrokerTopicConfigChanged(brokerAddr, topicConfigWrapper.getDataVersion())
  71. || registerFirst) {
  72. ConcurrentMap<String, TopicConfig> tcTable =
  73. topicConfigWrapper.getTopicConfigTable();
  74. if (tcTable != null) {
  75. // 创建或者更新topic 路由元信息
  76. for (Map.Entry<String, TopicConfig> entry : tcTable.entrySet()) {
  77. // 根据topicConfig创建QueueData数据结构,然后更新topicQueueTable
  78. this.createAndUpdateQueueData(brokerName, entry.getValue());
  79. }
  80. }
  81. }
  82. }
  83. // Step4:更新BrokerLiveInfo,存储状态正常的Broker信息表,BrokerLiveInfo是执行路由删除操作的重要依据
  84. BrokerLiveInfo prevBrokerLiveInfo = this.brokerLiveTable.put(brokerAddr,
  85. new BrokerLiveInfo(
  86. System.currentTimeMillis(),
  87. topicConfigWrapper.getDataVersion(),
  88. channel,
  89. haServerAddr));
  90. if (null == prevBrokerLiveInfo) {
  91. log.info("new broker registered, {} HAServer: {}", brokerAddr, haServerAddr);
  92. }
  93. // Step5:注册Broker的过滤器Server地址列表,一个Broker上会关联多个FilterServer消息过滤服务器
  94. // 如果Broker是从节点,需要查找该Broker的主节点信息,并更新对应的masterAddr属性
  95. if (filterServerList != null) {
  96. if (filterServerList.isEmpty()) {
  97. this.filterServerTable.remove(brokerAddr);
  98. } else {
  99. this.filterServerTable.put(brokerAddr, filterServerList);
  100. }
  101. }
  102. // Broker 是从节点
  103. if (MixAll.MASTER_ID != brokerId) {
  104. // Broker主节点地址
  105. String masterAddr = brokerData.getBrokerAddrs().get(MixAll.MASTER_ID);
  106. if (masterAddr != null) {
  107. BrokerLiveInfo brokerLiveInfo = this.brokerLiveTable.get(masterAddr);
  108. // 更新masterAddr
  109. if (brokerLiveInfo != null) {
  110. result.setHaServerAddr(brokerLiveInfo.getHaServerAddr());
  111. result.setMasterAddr(masterAddr);
  112. }
  113. }
  114. }
  115. } finally {
  116. this.lock.writeLock().unlock();
  117. }
  118. } catch (Exception e) {
  119. log.error("registerBroker Exception", e);
  120. }
  121. return result;
  122. }