上一篇 基于 IDEA 搭建 RocketMQ-4.6 源码环境 我们搭建并跑通了 rocketmq
的源码环境 .
本文我们紧接上文, 继续基于源码搭建并运行 broker
主从架构.
broker 主从架构只需要修改 broker.conf 文件即可, 其他地方与单节点没有差异.
第四章节主要是描述搭建过程中遇到的问题, 以及处理过程, 可以选择性跳过.
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/
).
我们可以从官网提供的异步模式中提取两个现成的配置文件, 比如 broker-a.conf
、broker-a-s.conf
文件, 将他们放在 rocketmq/conf/
目录下
然后启动 broker 程序时, 需要指定对应的配置文件.
这里有个细节:
当我们把两个 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)
关于以上两点, 我们放在第四章节详细说.
一开始, 我直接把官方提供的两个配置文件, 直接丢到了 rocketmq/conf 目录下, 然后启动 namesrv 成功, 启动 broker-a 成功.
当启动 broker-a-s 时, 提示如下:
这个错误提示大致意思是: broker-a-s 节点已经启动了. 说明他跟 broker-a 哪里冲突了, 就类似于端口占用冲突一样.
然后根据错误提示的代码入口(org.apache.rocketmq.store.DefaultMessageStore#start
), 开始打断点调试:
从图中可以看出, 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
从上图可知, broker 启动时, 会读取命令行参数. 然后把参数重新赋值给 messageStoreConfig
对象中的字段, 此时立马就想到, 我们程序运行时通过 -c
指定 broker.conf
配置文件.
因此可以得知, 我们可以把 storePathRootDir
属性配置到 broker-a-s.conf 文件中, 而 broker-a.conf 修改与否都不影响.
storePathRootDir = /Users/xxxx/store-a-s
当 问题一 解决后, 我们继续启动 broker-a-s 节点, 就会引发如下问题:
当有了上面的经验后, 立马就想到, 因为 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 的分析工具, 查看代码的使用情况.
从上面可以看出, 有两个地方是基于 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 之外的端口被哪些功能占用了, 并非本文的重点, 会放在后面的文章再去分析.
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 节点监听端口, 这个很重要