上一篇RocketMq源码调试环境搭建已经讲解了如何编译、调试及配置从github上拉下来的rocketmq项目。接下来开始逐步分析rocketmq nameserver源码。
第一步,先找到入口函数org.apache.rocketmq.namesrv.NamesrvStartup这个类里面的main函数:
public static void main(String[] args) {
main0(args);
}
很显然,对方并不想直接告诉我他到底在这里面干啥了。好吧,咱们继续往下看:
public static NamesrvController main0(String[] args) {
try {
//创建NamesrvController
NamesrvController controller = createNamesrvController(args);
//启动NamesrvController
start(controller);
// 下面不用看了,不是重点,看了也没用
String tip = "The Name Server boot success. serializeType=" + RemotingCommand.getSerializeTypeConfigInThisServer();
log.info(tip);
System.out.printf("%s%n", tip);
return controller;
} catch (Throwable e) {
e.printStackTrace();
System.exit(-1);
}
return null;
}
总结下来,上面的代码就两个重点:
- 如何创建NamesrvController对象
- 如何启动NamesrvController
1.创建NamesrvController对象
public static NamesrvController createNamesrvController(String[] args) throws IOException, JoranException {
// 设置版本号
System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, Integer.toString(MQVersion.CURRENT_VERSION));
//PackageConflictDetect.detectFastjson();
// 构建命令行选项-h,-c
Options options = ServerUtil.buildCommandlineOptions(new Options());
// 解析命令行选项,生成一个commandLine对象
commandLine = ServerUtil.parseCmdLine("mqnamesrv", args, buildCommandlineOptions(options), new PosixParser());
if (null == commandLine) {
System.exit(-1);
return null;
}
// 创建一个NamesrvConfig对象,里面的属性默认会查找rocketmq.home.dir参数或ROCKETMQ_HOME环境变量
final NamesrvConfig namesrvConfig = new NamesrvConfig();
// 其实重点就是获取这两个配置对象
final NettyServerConfig nettyServerConfig = new NettyServerConfig();
nettyServerConfig.setListenPort(9876);
// 读取配置文件,并更新namesrvConfig
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,那么就打印namesrvConfig和nettyServerConfig后结束程序
if (commandLine.hasOption('p')) {
InternalLogger console = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_CONSOLE_NAME);
MixAll.printObjectProperties(console, namesrvConfig);
MixAll.printObjectProperties(console, nettyServerConfig);
System.exit(0);
}
// 把读取到的配置文件全部设置到namesrvConfig中
MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), namesrvConfig);
// 如果没有找搭配参数rocketmq.home.dir或者ROCKETMQ_HOME环境变量,那么就直接结束程序
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);
//这个才是真正的创建NamesrvController对象,前提是需要准备好namesrvConfig和nettyServerConfig
final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig);
// 合并所有的配置
// remember all configs to prevent discard
controller.getConfiguration().registerConfig(properties);
return controller;
}
上面这个代码重点关注一行,就是如何创建NamesrvController:
final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig);
很明显,想要创建NamesrvController,就先要得到namesrvConfig和nettyServerConfig这两个配置对象。
那么我们的子目标就开始分解成
- 获取NamesrvConfig对象
- 获取NettyServerConfig对象
1.1:创建NamesrvConfig
在createNamesrvController方法中找到下面这行代码:
final NamesrvConfig namesrvConfig = new NamesrvConfig();
在创建NamesrvConfig对象的时候需要进去看一下里面都干啥了。主要是有一些属性的初始化的工作,不过值得我们在平时的编码工作中借鉴。
public class NamesrvConfig {
private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);
// 初始化rocketmqHome
private String rocketmqHome = System.getProperty(MixAll.ROCKETMQ_HOME_PROPERTY, System.getenv(MixAll.ROCKETMQ_HOME_ENV));
// 初始化kvConfigPath路径,用来持久化一些key:value键值对数据;路径就是用户目录后接"/namesrv/kvConfig.json"
private String kvConfigPath = System.getProperty("user.home") + File.separator + "namesrv" + File.separator + "kvConfig.json";
// 初始化configStorePath路径,这个很明显就是namesrv的服务器配置文件;路径是用户目录后接"/namesrv/namesrv.properties"
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;
// 下面get、set方法可不看
public boolean isOrderMessageEnable() {
return orderMessageEnable;
}
public void setOrderMessageEnable(boolean orderMessageEnable) {
this.orderMessageEnable = orderMessageEnable;
}
public String getRocketmqHome() {
return rocketmqHome;
}
public void setRocketmqHome(String rocketmqHome) {
this.rocketmqHome = rocketmqHome;
}
public String getKvConfigPath() {
return kvConfigPath;
}
public void setKvConfigPath(String kvConfigPath) {
this.kvConfigPath = kvConfigPath;
}
public String getProductEnvName() {
return productEnvName;
}
public void setProductEnvName(String productEnvName) {
this.productEnvName = productEnvName;
}
public boolean isClusterTest() {
return clusterTest;
}
public void setClusterTest(boolean clusterTest) {
this.clusterTest = clusterTest;
}
public String getConfigStorePath() {
return configStorePath;
}
public void setConfigStorePath(final String configStorePath) {
this.configStorePath = configStorePath;
}
}
先小结一下:
通过new NamesrvConfig()创建NamesrvConfig对象,在这个过程中,我们已经可以确定rocketmqHome这个参数可从两个地方配置;
第一个就是jvm启动参数中带上"-Drocketmq.home.dir="指定目标路径
第二个就是直接在环境变量中配置"ROCKETMQ_HOME"
其次,我们可以看到一些配置文件路径可以在对象创建的时候就直接赋值,可以理解为“约定大于配置”的思路;即使有需要自定义的路径,也可以通过提供的set方法再赋值。
1.2:创建NettyServerConfig
在createNamesrvController方法中找到创建NettyServerConfig的代码:
final NettyServerConfig nettyServerConfig = new NettyServerConfig();
进去看一下,这里面都是一些netty server需要的配置参数,下面是默认参数。
public class NettyServerConfig implements Cloneable {
// 默认监听8888端口
private int listenPort = 8888;
// 默认是8个工作线程;下面都是针对netty的一些配置,看不明白暂时忽略
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;
// 是否使用epoll原生Selector
private boolean useEpollNativeSelector = false;
// 下面get、set方法就不贴上来了
}
好吧,看到这里,没有啥大收获。下面继续回到createNamesrvController
// 设置版本号
System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, Integer.toString(MQVersion.CURRENT_VERSION));
//PackageConflictDetect.detectFastjson();
// 构建命令行选项-h,-c
Options options = ServerUtil.buildCommandlineOptions(new Options());
// 解析命令行选项,生成一个commandLine对象
commandLine = ServerUtil.parseCmdLine("mqnamesrv", args, buildCommandlineOptions(options), new PosixParser());
if (null == commandLine) {
System.exit(-1);
return null;
}
上面的代码就做了一件事情,解析命令行参数args,类似-c、-h、-p等参数
// 创建一个NamesrvConfig对象,里面的属性默认会查找rocketmq.home.dir参数或ROCKETMQ_HOME环境变量
final NamesrvConfig namesrvConfig = new NamesrvConfig();
// 其实重点就是获取这两个配置对象
final NettyServerConfig nettyServerConfig = new NettyServerConfig();
nettyServerConfig.setListenPort(9876);
// 读取配置文件,并更新namesrvConfig
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();
}
}
上面这个就很眼熟了,先创建两个配置对象,这两个对象中的属性已经有默认值了;
然后下一步就马上把服务的监听端口修改成了9876,所以namesrv服务端口9876就是这么来的。
后面开始判断命令行参数中是否包含配置文件,也就是"-c 配置文件路径"这样的参数;如果有自定义配置文件的话,那么直接读取成一个Properties对象,这是一个注意点,表示namesrv的配置文件一定要是.properties格式的,要不然肯定是不行的。
最后就是根据读进来的配置去更新两个对象中的属性值,原理就是用了反射技术,通过调用set方法去重新设置配置对象里面的属性值。感兴趣的小伙伴可以去了解一下这段代码底层的实现:
MixAll.properties2Object(properties, namesrvConfig);
下面这个就不解释了,就是纯打印。如果命令行参数包含"-p",把所有配置打印到控制台。
// 如果启动命令中有-p,那么就打印namesrvConfig和nettyServerConfig后结束程序
if (commandLine.hasOption('p')) {
InternalLogger console = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_CONSOLE_NAME);
MixAll.printObjectProperties(console, namesrvConfig);
MixAll.printObjectProperties(console, nettyServerConfig);
System.exit(0);
}
下面这行得好好解释一下:
// 把读取到的配置文件全部设置到namesrvConfig中
MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), namesrvConfig);
举个例子,假如命令行是这样的:
-rocketmqHome /home/rocketmq -kvConfigPath /home/namesrv/kv.json -configStorePath /home/namesrv/config.properties
就直接把这些参数解析成Properties,并且更新到namesrvConfig属性中去。
最后,都是一下健壮性判断、日志等相关配置,不是重点。
// 如果没有找搭配参数rocketmq.home.dir或者ROCKETMQ_HOME环境变量,那么就直接结束程序
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);
//这个才是真正的创建NamesrvController对象,前提是需要准备好namesrvConfig和nettyServerConfig
final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig);
// 合并所有的配置
// remember all configs to prevent discard
controller.getConfiguration().registerConfig(properties);
return controller;
这样,NamesrvController对象创建成功。以上的一些编码细节可以学习并应用到我们的日常工作当中去,这样写出来的代码才是开发友好型的代码。
如何启动NamesrvController留到下次再分析。