基于源码搭建运行 RocketMQ 主从架构

942 阅读8分钟

前言

上一篇 基于 IDEA 搭建 RocketMQ-4.6 源码环境 我们搭建并跑通了 rocketmq 的源码环境 .

本文我们紧接上文, 继续基于源码搭建并运行 broker 主从架构.

  • 1 个 NameServer 节点 (与前文一样)
  • 2 个 Broker 节点, 一个作为 Master, 一个作为 Slave
  • 1 个 Producer 生产者 (与前文一样)
  • 1 个 Consumer 消费者 (与前文一样)

broker 主从架构只需要修改 broker.conf 文件即可, 其他地方与单节点没有差异.

第四章节主要是描述搭建过程中遇到的问题, 以及处理过程, 可以选择性跳过.

正文

一、回顾 RocketMQ 的主从部署架构

rocketmq_architecture_3.png

  • Broker 部署相对复杂,Broker 分为 Master 与 Slave

  • master 与 slave 之间同步数据既支持同步(sync), 也支持异步(async)

  • 一个Master可以对应多个Slave,但是一个Slave只能对应一个Master,

  • Master 与 Slave 的对应关系通过指定相同的 BrokerName,

  • 不同的BrokerId 来定义,BrokerId为0表示Master,非0表示Slave。

  • Master也可以部署多个。

  • 每个 Broker 与 NameServer 集群中的所有节点建立长连接,定时注册Topic信息到所有NameServer。

    注意:当前RocketMQ版本在部署架构上支持一Master多Slave,但只有BrokerId=1的从服务器才会参与消息的读负载。

二、官方提供的 两主两从 架构

RocketMQ 官方提供了 两主两从 (异步 和 同步)的配置文件( rocketmq/distribution/conf/ ).

YcLcss.png

三、基于官网的配置搭建一主一从架构

我们可以从官网提供的异步模式中提取两个现成的配置文件, 比如 broker-a.confbroker-a-s.conf 文件, 将他们放在 rocketmq/conf/ 目录下

sszatM.png

然后启动 broker 程序时, 需要指定对应的配置文件.

izYI7y.png

这里有个细节:

当我们把两个 broker 节点部署在两台不同的机器时, 可以直接采用官网提供的配置, 无需进行修改.

但是我们是在同一台机器, 启动两个 broker 节点. 那么我们就需要注意 默认的配置文件 中缺少了包括但不限于以下配置:

  • broker 节点的监听端口(如果不配置, 那么启动第二个节点就会提示端口已占用.)

    关于端口的配置, 这里还有另外一个大坑!!! 后面详细说

  • broker 保存数据的存储路径.(如果不配置, 那么两个节点的存储路径会冲突.)

    java.lang.RuntimeException: Lock failed,MQ already started
    	at org.apache.rocketmq.store.DefaultMessageStore.start(DefaultMessageStore.java:222)
    	at org.apache.rocketmq.broker.BrokerController.start(BrokerController.java:853)
    	at org.apache.rocketmq.broker.BrokerStartup.start(BrokerStartup.java:64)
    	at org.apache.rocketmq.broker.BrokerStartup.main(BrokerStartup.java:58)
    

    关于以上两点, 我们放在第四章节详细说.

四、搭建过程中遇到的问题

4.1 问题一

一开始, 我直接把官方提供的两个配置文件, 直接丢到了 rocketmq/conf 目录下, 然后启动 namesrv 成功, 启动 broker-a 成功.

当启动 broker-a-s 时, 提示如下:

VCaI2j.png

这个错误提示大致意思是: broker-a-s 节点已经启动了. 说明他跟 broker-a 哪里冲突了, 就类似于端口占用冲突一样.

然后根据错误提示的代码入口(org.apache.rocketmq.store.DefaultMessageStore#start), 开始打断点调试:

C0DFM2.png

从图中可以看出, lockFile 对象中有个 path 字段, 它指向的是 /Users/用户名/store/lock 文件.

此时我的猜想如下:

broker 节点启动时, 会在指定目录(/Users/用户名/store/)写入一个名字为 lock 的文件, 表示 broker 节点已经启动了.(这只是猜想, 实际情况虽然不一定如此, 但是不会偏离很远.)

然后我们查看 broker-a.conf 的配置文件, 文件中并没有 哪个配置 指定了 /Users/用户名/store/lock 这个路径

继续跟踪源码 org.apache.rocketmq.store.DefaultMessageStore#DefaultMessageStore, 看看 lockFile 是在哪里初始化的(也就是 path 属性是什么时候赋值的).

File file = new File(StorePathConfigHelper.getLockFile(messageStoreConfig.getStorePathRootDir()));
MappedFile.ensureDirOK(file.getParent());
lockFile = new RandomAccessFile(file, "rw");
public class MessageStoreConfig {
    // 用户目录 + "/" + store
    private String storePathRootDir = System.getProperty("user.home") + File.separator + "store";
  
}

由上可知, storePathRootDir 字段默认存储的就是 /Users/用户名/store/, 但是至此, 我们还不知道怎么配置这个属性.

所以我们需要借助 IDEA 找到 MessageStoreConfig 是在哪里实例化的.

org.apache.rocketmq.broker.BrokerStartup#createBrokerController

dLs1n6.png

从上图可知, broker 启动时, 会读取命令行参数. 然后把参数重新赋值给 messageStoreConfig 对象中的字段, 此时立马就想到, 我们程序运行时通过 -c 指定 broker.conf 配置文件.

izYI7y.png

因此可以得知, 我们可以把 storePathRootDir 属性配置到 broker-a-s.conf 文件中, 而 broker-a.conf 修改与否都不影响.

storePathRootDir = /Users/xxxx/store-a-s 

4.2 问题二

问题一 解决后, 我们继续启动 broker-a-s 节点, 就会引发如下问题:

fZzYv3.png

当有了上面的经验后, 立马就想到, 因为 broker-a-s 节点没有配置监听端口, 所以导致它跟 broker-a 节点的 端口冲突 了.

所以, 我们只需要找到对应的配置项是哪个就行.

继续 debug 源码

org.apache.rocketmq.broker.BrokerStartup#createBrokerController

nettyServerConfig.setListenPort(10911);
if (commandLine.hasOption('c')) {
   String file = commandLine.getOptionValue('c');
   if (file != null) {
	// 把 broker-a-s.conf 的配置覆盖掉 nettyServerConfig 中原有的配置
      MixAll.properties2Object(properties, nettyServerConfig);             
               
   }
}

默认 netty server 的端口是 10911, 如果 broker-a-s.conf 中配置了 listenPort 属性, 那么会进行覆盖.

所以, 直接在 broker-a-s.conf 中重新定义一个监听端口即可:

listenPort = 10912 // 默认端口 + 1

继续启动程序, 依然提示端口占用的报错. 下意识以为 10912 端口也被哪个应用占用了, 然后继续配置成 10913. 直到配置成 10915 才运行成功 (这里说来也巧, 当时比较头铁, 一直在尝试端口 + 1.)

此时当两个 broker 节点都启动后, 我通过 端口查看命令, 去验证 10911 - 10915 端口是否真的被占用了.

结果如下:

可以看到除了 10911 - 10915 之外, 我还额外标注了几个端口(这是在我明白问题所在后总结的, 可以先忽略).

=============broker-a

10909 (被占用)(pid:77589)

10910

10911 (被占用)(pid:77589)

10912(被占用)(pid:77589)

==============broker-a-s

10913(被占用)(pid:93439)

10914

10915(被占用)(pid:93439)

10916(被占用)(pid:93439)

10917

此时得出的结论是, broker-a 启动时, 它不仅打开了一个 netty server 的 10911(listenPort) 端口, 还打开了一个 10912 (listenPort + 1 )的端口(作用未知.)

既然如此, 那为什么启动 broker-a-s 时, 10914不行呢?

讲道理,如果配置 netty server 占用端口是 10194, 那个也只会再额外占用一个 10915 端口. (启动前已验证 10914, 10915 未被占用) . 理论上不影响 broker-a-s 不会因为端口占用而运行不起来的.

继续追踪源码

org.apache.rocketmq.broker.BrokerStartup#createBrokerController

如果一行行分析源码很浪费时间, 于是我们可以先大胆猜想(更多是经验而言), 10912 端口可能是根据 10911 计算 来的,

于是再次借助 IDEA 的分析工具, 查看代码的使用情况.

i39qir.png

从上面可以看出, 有两个地方是基于 listenPort 属性计算的, 于是有了如下推论:

已知 broker-a 至少占用了 10911(listenPort), 10912 两个端口,

当 broker-a-s 用 10914 当 listenPort 时, 10915 自然也会被占用, 同时 listenPort - 2= 10912 也要被占用.

但是 10912 已经被 broker-a 占用了, 于是 IDEA 控制台抛出了 “端口占用的异常信息”.

于是, 我们再回过头来看, broker-a(10911) , broker-a-s (10915) 都启动时的端口占用情况, 就很合理了.

=============broker-a

10909 (listenPort-2)(被占用)(pid:77589)

10910

10911 (listenPort)(被占用) (pid:77589)

10912(listenPort+1)(被占用) (pid:77589)

==============broker-a-s

10913(listenPort-2)(被占用) (pid:93439)

10914

10915(listenPort)(被占用) (pid:93439)

10916(listenPort+1)(被占用)(pid:93439)

10917

到此为止, 我们终于把 问题 二 分析明白了, 至于除了 listenPort 之外的端口被哪些功能占用了, 并非本文的重点, 会放在后面的文章再去分析.

五、单机一主一从的最终配置

官网提供的配置

Wr1oCA.png

修改后的配置

broker-a.conf

brokerClusterName = DefaultCluster
brokerName = broker-a // Master 与 Slave 的对应关系通过指定相同的 BrokerName,
brokerId = 0 // 不同的 BrokerId 来定义,BrokerId 为 0 表示 Master,非 0 表示 Slave。
deleteWhen = 04
fileReservedTime = 48
brokerRole = ASYNC_MASTER // 角色
flushDiskType = ASYNC_FLUSH
autoCreateTopicEnable = true
storePathRootDir = /Users/xxxx/store-a // 存储路径
namesrvAddr = localhost:9876 // namesrv 的地址
listenPort = 10911 // broker 节点监听端口, 这个很重要

Broker-a-s.conf

brokerClusterName = DefaultCluster
brokerName = broker-a // Master 与 Slave 的对应关系通过指定相同的 BrokerName
brokerId = 1 // 不同的 BrokerId 来定义,BrokerId 为 0 表示 Master,非 0 表示 Slave。
deleteWhen = 04
fileReservedTime = 48
brokerRole = SLAVE // 角色
flushDiskType = ASYNC_FLUSH
autoCreateTopicEnable = true
namesrvAddr = localhost:9876 // namesrv 的地址
storePathRootDir = /Users/xxxx/store-a-s // 存储路径
listenPort = 10921 // broker 节点监听端口, 这个很重要