rocketmq 启动_RocketMQ源码分析:NameServer启动流程

9d63c41f85c9bd9da8a8fa19c3e41a71.png

本文首发于Ressmix个人站点:https://www.tpvlog.com

上一章,我们已经搭建好了RocketMQ的源码环境。从本章开始,我们正式进入RocketMQ的源码分析环节。分析一个开源框架的源码,必然从开源框架的入口开始。

在RocketMQ使用的时候,第一个步骤一定是先启动NameServer,那么我们就先来分析NameServer启动这块的源码。

一、启动入口

1.1 启动脚本

我之前在《RocketMQ生产部署》一章中,讲解过NameServer的启动,启动脚本在distribution模块的bin目录下——mqnamesrv

这个脚本中有极为关键的一行命令用于启动NameServer进程:

1sh ${ROCKETMQ_HOME}/bin/runserver.sh org.apache.rocketmq.namesrv.NamesrvStartup $@

可以看到,上面的命令其实是执行了runserver.sh这个脚本,然后通过这个脚本去启动了NamesrvStartup这个Java类,下面是runserver.sh这个脚本的一些内容:

 1JAVA_OPT="${JAVA_OPT} -server -Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
2JAVA_OPT="${JAVA_OPT} -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled -XX:SurvivorRatio=8  -XX:-UseParNewGC"
3JAVA_OPT="${JAVA_OPT} -verbose:gc -Xloggc:${GC_LOG_DIR}/rmq_srv_gc_%p_%t.log -XX:+PrintGCDetails"
4JAVA_OPT="${JAVA_OPT} -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m"
5JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow"
6JAVA_OPT="${JAVA_OPT} -XX:-UseLargePages"
7JAVA_OPT="${JAVA_OPT} -Djava.ext.dirs=${JAVA_HOME}/jre/lib/ext:${BASE_DIR}/lib"
8#JAVA_OPT="${JAVA_OPT} -Xdebug -Xrunjdwp:transport=dt_socket,address=9555,server=y,suspend=n"
9JAVA_OPT="${JAVA_OPT} ${JAVA_OPT_EXT}"
10JAVA_OPT="${JAVA_OPT} -cp ${CLASSPATH}"
11
12$JAVA ${JAVA_OPT} $@

其实就是通过java命令去执行NamesrvStartup.main()方法,启动一个JVM进程:

b343c8ceadbde665e4082cddafb6ea41.png

1.2 NamesrvStartup类

我们来看下NamesrvStartup类的main方法:

 1public class NamesrvStartup { 2 3    // 忽略这些跟主体逻辑相关度不大的分支代码 4    private static InternalLogger log; 5    private static Properties properties = null; 6    private static CommandLine commandLine = null; 7 8    public static void main(String[] args) { 9        main0(args);10    }1112    public static NamesrvController main0(String[] args) {1314        try {15            NamesrvController controller = createNamesrvController(args);16            start(controller);17            String tip = "The Name Server boot success. serializeType=" + RemotingCommand.getSerializeTypeConfigInThisServer();18            log.info(tip);19            System.out.printf("%s%n", tip);20            return controller;21        } catch (Throwable e) {22            e.printStackTrace();23            System.exit(-1);24        }2526        return null;27    }2829    //...30}

可以看到,在main()方法内部最核心的逻辑是创建了一个NamesrvController对象,然后调用start(controller)方法来启动这个Controller。

二、创建NamesrvController

NamesrvController是什么呢?其实从命名就可以看出这是一个控制器,熟悉Spring的童鞋应该不会陌生,Controller一般用于接受请求,那么NameServer接受什么请求呢?当然是Broker的注册请求、心跳请求,以及Producer和Consumer的拉取路由信息请求。

NamesrvController这个组件,就是NameServer专门用来接受Broker和客户端的网络请求的一个组件:

f1c2db9964dd7a9b3fd57b22735cafdc.png

2.1 构建NameServer配置对象

我们来看下创建NamesrvController的代码:

 1public static NamesrvController createNamesrvController(String[] args) throws IOException, JoranException { 2    System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, Integer.toString(MQVersion.CURRENT_VERSION)); 3    //PackageConflictDetect.detectFastjson(); 4 5    // 解析命令行中的参数 6    Options options = ServerUtil.buildCommandlineOptions(new Options()); 7    commandLine = ServerUtil.parseCmdLine("mqnamesrv", args, buildCommandlineOptions(options), new PosixParser()); 8    if (null == commandLine) { 9        System.exit(-1);10        return null;11    }1213    final NamesrvConfig namesrvConfig = new NamesrvConfig();14    final NettyServerConfig nettyServerConfig = new NettyServerConfig();15    nettyServerConfig.setListenPort(9876);16    if (commandLine.hasOption('c')) {17        String file = commandLine.getOptionValue('c');18        if (file != null) {19            // 读取外部配置文件的内容20            InputStream in = new BufferedInputStream(new FileInputStream(file));21            properties = new Properties();22            properties.load(in);23            MixAll.properties2Object(properties, namesrvConfig);24            MixAll.properties2Object(properties, nettyServerConfig);2526            namesrvConfig.setConfigStorePath(file);2728            System.out.printf("load config properties file OK, %s%n", file);29            in.close();30        }31    }3233    if (commandLine.hasOption('p')) {34        InternalLogger console = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_CONSOLE_NAME);35        MixAll.printObjectProperties(console, namesrvConfig);36        MixAll.printObjectProperties(console, nettyServerConfig);37        System.exit(0);38    }3940    MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), namesrvConfig);4142    // 如果ROCKETMQ_HOME为空,就报错退出,这就是我们为什么必须设置ROCKETMQ_HOME环境变量的原因43    if (null == namesrvConfig.getRocketmqHome()) {44        System.out.printf("Please set the %s variable in your environment to match the location of the RocketMQ installation%n", MixAll.ROCKETMQ_HOME_ENV);45        System.exit(-2);46    }4748    // 日志相关配置49    LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();50    JoranConfigurator configurator = new JoranConfigurator();51    configurator.setContext(lc);52    lc.reset();53    configurator.doConfigure(namesrvConfig.getRocketmqHome() + "/conf/logback_namesrv.xml");5455    log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);5657    MixAll.printObjectProperties(log, namesrvConfig);58    MixAll.printObjectProperties(log, nettyServerConfig);5960    final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig);6162    // remember all configs to prevent discard63    controller.getConfiguration().registerConfig(properties);6465    return controller;66}

别看上面代码一大堆,其实核心就做了一件事情:解析命名行中的相关参数,然后构建出两个配置对象——NamesrvConfigNettyServerConfig

我们在启动NameServer的时候,是使用mqnamesrv命令来启动的,启动的时候可能会在命令行里带入一些参数,所以很上面那块代码,就是解析一下我们传递进去的一些命令行参数而已!

这里最关键的是创建了两个配置对象:

1final NamesrvConfig namesrvConfig = new NamesrvConfig();2final NettyServerConfig nettyServerConfig = new NettyServerConfig();3nettyServerConfig.setListenPort(9876);

NamesrvConfig:包含的是NameServer自身运行的一些配置参数,NameServer默认监听请求的端口号是9876,用来接收Broker和客户端的请求;NettyServerConfig:包含的是用于接收网络请求的Netty服务器的配置参数。

cc6bbeb20bb5e1ec9183c90b603aee84.png

我们以NamesrvConfig为例,看下里面的内容,其实就是些NameServer的默认配置:

 1public class NamesrvConfig { 2    private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME); 3 4    private String rocketmqHome = System.getProperty(MixAll.ROCKETMQ_HOME_PROPERTY, System.getenv(MixAll.ROCKETMQ_HOME_ENV)); 5    private String kvConfigPath = System.getProperty("user.home") + File.separator + "namesrv" + File.separator + "kvConfig.json"; 6    private String configStorePath = System.getProperty("user.home") + File.separator + "namesrv" + File.separator + "namesrv.properties"; 7    private String productEnvName = "center"; 8    private boolean clusterTest = false; 9    private boolean orderMessageEnable = false;1011    //...12}

2.2 解析配置文件

我们具体来看下是如何解析配置文件的:

 1// 覆盖配置文件中的配置到配置类中 2if (commandLine.hasOption('c')) { 3    String file = commandLine.getOptionValue('c'); 4    if (file != null) { 5        InputStream in = new BufferedInputStream(new FileInputStream(file)); 6        properties = new Properties(); 7        properties.load(in); 8        MixAll.properties2Object(properties, namesrvConfig); 9        MixAll.properties2Object(properties, nettyServerConfig);1011        namesrvConfig.setConfigStorePath(file);1213        System.out.printf("load config properties file OK, %s%n", file);14        in.close();15    }16}1718// 打印配置信息19if (commandLine.hasOption('p')) {20    InternalLogger console = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_CONSOLE_NAME);21    MixAll.printObjectProperties(console, namesrvConfig);22    MixAll.printObjectProperties(console, nettyServerConfig);23    System.exit(0);24}

上面的代码意思是说:在启动NameServer的时候,如果用-c选项带上了一个配置文件路径,那么运行到上面的代码,就会把配置文件里的配置,放入两个核心配置类里去。比如有一个配置文件是:nameserver.properties,里面有一个配置是serverWorkerThreads=16,那么上面的代码就会读取出来这个配置,然后覆盖到NettyServerConfig里去!

三、启动NamesrvController

我们回到主流程,构建完了NamesrvController对象后,就执行start()方法来启动NamesrvController了:

 1public static NamesrvController start(final NamesrvController controller) throws Exception { 2 3    if (null == controller) { 4        throw new IllegalArgumentException("NamesrvController is null"); 5    } 6 7    // 初始化NamesrvController 8    boolean initResult = controller.initialize(); 9    if (!initResult) {10        controller.shutdown();11        System.exit(-3);12    }1314    // 注册一个shutdown钩子,JVM关闭时会执行15    Runtime.getRuntime().addShutdownHook(new ShutdownHookThread(log, new Callable() {16        @Override17        public Void call() throws Exception {18            controller.shutdown();19            return null;20        }21    }));2223    // 启动NamesrvController24    controller.start();2526    return controller;27}

3.1 初始化NamesrvController

start方法的核心就是先执行controller.initialize()初始化NamesrvController,然后执行controller.start()启动NamesrvController。我们先来看下initialize方法:

 1public boolean initialize() { 2 3    // kvConfigManager用于管理KV配置 4    this.kvConfigManager.load(); 5 6    // 构建NettyRemotingServer对象,其实就是一个Netty网络服务器 7    this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService); 8 9    // 创建Netty服务器的工作线程池10    this.remotingExecutor =11        Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), new ThreadFactoryImpl("RemotingExecutorThread_"));1213    // 把工作线程池将给Netty管理14    this.registerProcessor();1516    // 启动一个定时任务,扫描那些没发送心跳的Broker17    this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {18        @Override19        public void run() {20            NamesrvController.this.routeInfoManager.scanNotActiveBroker();21        }22    }, 5, 10, TimeUnit.SECONDS);2324    // 启动一个定时任务,打印KV配置信息25    this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {26        @Override27        public void run() {28            NamesrvController.this.kvConfigManager.printAllPeriodically();29        }30    }, 1, 10, TimeUnit.MINUTES);3132    //...33    return true;34}

controller.initialize()方法,核心就是把NettyRemotingServer网络服务器组件给构造了出来,其内部用到了Netty的核心类——ServerBootstrap:

 1public NettyRemotingServer(final NettyServerConfig nettyServerConfig, 2    final ChannelEventListener channelEventListener) { 3    super(nettyServerConfig.getServerOnewaySemaphoreValue(), nettyServerConfig.getServerAsyncSemaphoreValue()); 4 5    // Netty核心类 6    this.serverBootstrap = new ServerBootstrap(); 7    this.nettyServerConfig = nettyServerConfig; 8    this.channelEventListener = channelEventListener; 910    //...11}

65fb75b103265441ccb818ad7caecc60.png

3.2 启动Netty Server

初始化完NamesrvController后,我们再来看下NamesrvController的启动:

1public void start() throws Exception {2    this.remotingServer.start();34    if (this.fileWatchService != null) {5        this.fileWatchService.start();6    }7}

NamesrvContorller的启动,核心就是内部的NettyRemotingServer的启动,这段代码没什么好说的,都是些Netty API的代码:

 1public void start() {
2
3    // 对Netty的各种配置,核心就是基于Netty的API去配置和启动一个网络服务器
4    this.defaultEventExecutorGroup = new DefaultEventExecutorGroup(
5        nettyServerConfig.getServerWorkerThreads(),
6        new ThreadFactory() {
7            private AtomicInteger threadIndex = new AtomicInteger(0); 8            @Override 9            public Thread newThread(Runnable r) {10                return new Thread(r, "NettyServerCodecThread_" + this.threadIndex.incrementAndGet());11            }12        });1314    prepareSharableHandlers();1516    ServerBootstrap childHandler =17        this.serverBootstrap.group(this.eventLoopGroupBoss, this.eventLoopGroupSelector)18        .channel(useEpoll() ? EpollServerSocketChannel.class : NioServerSocketChannel.class)19        .option(ChannelOption.SO_BACKLOG, 1024)20        .option(ChannelOption.SO_REUSEADDR, true)21        .option(ChannelOption.SO_KEEPALIVE, false)22        .childOption(ChannelOption.TCP_NODELAY, true)23        .childOption(ChannelOption.SO_SNDBUF, nettyServerConfig.getServerSocketSndBufSize())24        .childOption(ChannelOption.SO_RCVBUF, nettyServerConfig.getServerSocketRcvBufSize())25        .localAddress(new InetSocketAddress(this.nettyServerConfig.getListenPort()))26        .childHandler(new ChannelInitializer() {27            @Override28            public void initChannel(SocketChannel ch) throws Exception {29                ch.pipeline()30                    .addLast(defaultEventExecutorGroup, HANDSHAKE_HANDLER_NAME, handshakeHandler)31                    .addLast(defaultEventExecutorGroup,32                             encoder,33                             new NettyDecoder(),34                             new IdleStateHandler(0, 0, nettyServerConfig.getServerChannelMaxIdleTimeSeconds()),35                             connectionManageHandler,36                             serverHandler37                            );38            }39        });4041    if (nettyServerConfig.isServerPooledByteBufAllocatorEnable()) {42        childHandler.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);43    }4445    try {46        // 核心是这里,bind方法就是绑定和监听指定端口,默认是987647        ChannelFuture sync = this.serverBootstrap.bind().sync();48        InetSocketAddress addr = (InetSocketAddress) sync.channel().localAddress();49        this.port = addr.getPort();50    } catch (InterruptedException e1) {51        throw new RuntimeException("this.serverBootstrap.bind().sync() InterruptedException", e1);52    }5354    if (this.channelEventListener != null) {55        // 启动netty服务56        this.nettyEventExecutor.start();57    }5859    this.timer.scheduleAtFixedRate(new TimerTask() {60        @Override61        public void run() {62            try {63                NettyRemotingServer.this.scanResponseTable();64            } catch (Throwable e) {65                log.error("scanResponseTable exception", e);66            }67        }68    }, 1000 * 3, 1000);69}

992a791720b3b687f495ac072e630b76.png

三、总结

本章,我分析了NameServer的启动源码,了解到它最核心的就是基于Netty实现了一个网络服务器,然后监听默认的9876端口,这样就可以接收Broker和客户端的网络请求了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值