RocketMq Nameserver源码分析-01

上一篇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留到下次再分析。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容