RocketMq源码分析(二):NameServer启动流程

123 阅读5分钟

一.概要

nameserver是一个简单的topic路由注册中心,类似dubbo中的zookeeper,或者springcloud中的nacos,主要功能如下:

  • broker管理,nameserver接受broker集群注册信息,并保存下来,提供心跳机制,检查brocker是否活着
  • 保存路由信息,让producer和consumer进行消息的投递和消费

二.启动流程

2.1 main主方法的步骤

public class NamesrvStartup {

    public static void main(String[] args) {
        main0(args);
        controllerManagerMain();
    }

2.2 main0(args)方法

public static void main0(String[] args) {
    try {
        //解析一些命令参数,不重要,省略
        parseCommandlineAndConfigFile(args);
        // 创建一个controller,并启动服务
        createAndStartNamesrvController();
    } catch (Throwable e) {
        e.printStackTrace();
        System.exit(-1);
    }

}

可以看到,主要是两个步骤:解析命令参数和创建并启动服务

2.2.1 createAndStartNamesrvController方法

public static void createAndStartNamesrvController() throws Exception {
    // 创建一个controller并复制某些属性
    NamesrvController controller = createNamesrvController();
    // 启动nameserver(netty...)
    start(controller);
    String tip = "The Name Server boot success. serializeType=" + RemotingCommand.getSerializeTypeConfigInThisServer();
    log.info(tip);
    System.out.printf("%s Name Server 启动成功 %n", tip);
}

进入createNamesrvController()方法

public static NamesrvController createNamesrvController() {
    // 创建一个controller
    final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig, nettyClientConfig);
    // remember all configs to prevent discard
    // 将当前 properties 合并到项目的配置中,并且当前 properties 会覆盖项目中的配置
    controller.getConfiguration().registerConfig(properties);
    return controller;
}

这个nameserController创建成功后,接着会start()方法

public static NamesrvController start(final NamesrvController controller) throws Exception {

    if (null == controller) {
        throw new IllegalArgumentException("NamesrvController is null");
    }

    /** 初始化,核心做下面两件事
     *  1.处理netty相关:创建远程服务与工作线程
     *  2.开启定时任务[5s一次]:移除不活跃的broker
     */
    boolean initResult = controller.initialize();
    if (!initResult) {
        controller.shutdown();
        System.exit(-3);
    }
    /**
     * 关闭钩子,可以在关闭前进行一些操作
     * 在 kill pid的时候会被触发(rocketmq的启动脚本中用的就是kill -pid)
     * kill -9 pid 不会被触发
     */
    Runtime.getRuntime().addShutdownHook(new ShutdownHookThread(log, (Callable<Void>) () -> {
        controller.shutdown();
        return null;
    }));
    // 启动
    controller.start();


    return controller;
}

上面代码是nameserverController的启动的核心,主要做的事情如下

  • 调用initialize()方法
  • 注册钩子事件,在jvm关闭的时候,会触发钩子事件
  • 调用 start()方法,接下来我们一个个来看

A.调用initialize()方法

public boolean initialize() {
    // 加载 kv 配置
    loadConfig();

    // 创建 netty 远程服务,创建 NettyRemotingServer 和 NettyRemotingClient
    initiateNetworkComponents();

    //初始化一些队列和线程池
    initiateThreadExecutors();

    // 注册,
    // 1.就是把[上一步创建的defaultExecutor] defaultExecutor 注册到 remotingServer作为默认的处理,
    // 2.还会注册 GET_ROUTEINFO_BY_TOPIC
    registerProcessor();

    // 开启定时任务,每隔5s扫描一次broker,移除不活跃的broker
    startScheduleService();

    // Tls安全传输,我们不关注
    initiateSslContext();
    initiateRpcHooks();
    return true;
}

代码中,会启动netty服务,创建 NettyRemotingServer 和 NettyRemotingClient, 初始化一些队列和线程池,机型注册,还会开启一些定时任务,清除不活跃的broker,这一步是最核心的,接下来来一次来看一下核心代码逻辑

1)创建netty服务

private void initiateNetworkComponents() {
    this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);
    this.remotingClient = new NettyRemotingClient(this.nettyClientConfig);
}

我们以 NettyRemotingServer的创建为例,看代码

public NettyRemotingServer(final NettyServerConfig nettyServerConfig,
    final ChannelEventListener channelEventListener) {
    super(nettyServerConfig.getServerOnewaySemaphoreValue(), nettyServerConfig.getServerAsyncSemaphoreValue());
    this.serverBootstrap = new ServerBootstrap();
    this.nettyServerConfig = nettyServerConfig;
    this.channelEventListener = channelEventListener;
    // 创建了一个名为publicExecutor线程池
    this.publicExecutor = buildPublicExecutor(nettyServerConfig);
    //创建boosGroup
    this.eventLoopGroupBoss = buildBossEventLoopGroup();
    // 创建workGroup
    this.eventLoopGroupSelector = buildEventLoopGroupSelector();

    loadSslContext();
}
  1. serverBootstrap:创建netty服务端的启动类
  2. publicExecutor:这里创建了一个名为publicExecutor线程池,暂时并不知道这个线程有啥作用,先混个脸熟吧
  3. eventLoopGroupBosseventLoopGroupSelector线程组:熟悉netty的小伙伴应该对这两个线程很熟悉了,这就是netty用来处理连接事件与读写事件的线程了,eventLoopGroupBoss对应的是netty的boss线程组,eventLoopGroupSelector对应的是worker线程组

到这里,netty服务的准备工作本完成了。

2)初始化一些队列和线程池

private void initiateThreadExecutors() {
    this.defaultThreadPoolQueue = new LinkedBlockingQueue<>(this.namesrvConfig.getDefaultThreadPoolQueueCapacity());
    this.defaultExecutor = new ThreadPoolExecutor(this.namesrvConfig.getDefaultThreadPoolNums(), this.namesrvConfig.getDefaultThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.defaultThreadPoolQueue, new ThreadFactoryImpl("RemotingExecutorThread_")) {
        @Override
        protected <T> RunnableFuture<T> newTaskFor(final Runnable runnable, final T value) {
            return new FutureTaskExt<>(runnable, value);
        }
    };

    this.clientRequestThreadPoolQueue = new LinkedBlockingQueue<>(this.namesrvConfig.getClientRequestThreadPoolQueueCapacity());
    this.clientRequestExecutor = new ThreadPoolExecutor(this.namesrvConfig.getClientRequestThreadPoolNums(), this.namesrvConfig.getClientRequestThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.clientRequestThreadPoolQueue, new ThreadFactoryImpl("ClientRequestExecutorThread_")) {
        @Override
        protected <T> RunnableFuture<T> newTaskFor(final Runnable runnable, final T value) {
            return new FutureTaskExt<>(runnable, value);
        }
    };
}

3)netty注册一些默认的处理器

这些处理器,后面会详细讲解

private void registerProcessor() {
    if (namesrvConfig.isClusterTest()) {

        this.remotingServer.registerDefaultProcessor(new ClusterTestRequestProcessor(this, namesrvConfig.getProductEnvName()), this.defaultExecutor);
    } else {
        // Support get route info only temporarily
        ClientRequestProcessor clientRequestProcessor = new ClientRequestProcessor(this);
        this.remotingServer.registerProcessor(RequestCode.GET_ROUTEINFO_BY_TOPIC, clientRequestProcessor, this.clientRequestExecutor);
        // 注册操作
        this.remotingServer.registerDefaultProcessor(new DefaultRequestProcessor(this), this.defaultExecutor);
    }
}

4) 开启定时任务,清除不活跃的broker

private void startScheduleService() {
    /**
     * 移除不活跃的broker ,默认每5秒执行一次
     * scanNotActiveBroker逻辑: 取map中broker信息,  最后一次更新时间+更新间隔 < 当前时间,就close这个channel
     */
    this.scanExecutorService.scheduleAtFixedRate(NamesrvController.this.routeInfoManager::scanNotActiveBroker,
        5, this.namesrvConfig.getScanNotActiveBrokerInterval(), TimeUnit.MILLISECONDS);

    this.scheduledExecutorService.scheduleAtFixedRate(NamesrvController.this.kvConfigManager::printAllPeriodically,
        1, 10, TimeUnit.MINUTES);

    this.scheduledExecutorService.scheduleAtFixedRate(() -> {
        try {
            NamesrvController.this.printWaterMark();
        } catch (Throwable e) {
            LOGGER.error("printWaterMark error.", e);
        }
    }, 10, 1, TimeUnit.SECONDS);
}

B.调用 start()方法

public void start() throws Exception {
    // 启动nettyServer
    this.remotingServer.start();
}

将上一步创建的netty服务,进行启动,进入start()方法

2.2 controllerManagerMain()方法

public void start() {
    this.defaultEventExecutorGroup = new DefaultEventExecutorGroup(
        nettyServerConfig.getServerWorkerThreads(),
        new ThreadFactory() {

            private final AtomicInteger threadIndex = new AtomicInteger(0);

            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "NettyServerCodecThread_" + this.threadIndex.incrementAndGet());
            }
        });

    // 初始化 netty的四个handler,后面会添加到netty中的pipeline中
    prepareSharableHandlers();

    serverBootstrap.group(this.eventLoopGroupBoss, this.eventLoopGroupSelector)
        .channel(useEpoll() ? EpollServerSocketChannel.class : NioServerSocketChannel.class)
        .option(ChannelOption.SO_BACKLOG, 1024)
        .option(ChannelOption.SO_REUSEADDR, true)
        .childOption(ChannelOption.SO_KEEPALIVE, false)
        .childOption(ChannelOption.TCP_NODELAY, true)
            //TODO 绑定ip和端口
        .localAddress(new InetSocketAddress(this.nettyServerConfig.getBindAddress(),
            this.nettyServerConfig.getListenPort()))
        .childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            public void initChannel(SocketChannel ch) {
                ch.pipeline()
                        //handshakeHandler  处理握手操作,用来判断tls的开启状态
                    .addLast(defaultEventExecutorGroup, HANDSHAKE_HANDLER_NAME, handshakeHandler)

                        /**
                         * 批量添加五个handler
                         * encoder/NettyDecoder:处理报文的编解码操作
                         * IdleStateHandler:处理心跳
                         * connectionManageHandler:处理连接请求
                         * serverHandler:处理读写请求=> TODO 用来处理broker注册消息、producer/consumer获取topic消息的
                         */
                    .addLast(defaultEventExecutorGroup,
                        encoder,
                        new NettyDecoder(),
                        new IdleStateHandler(0, 0,
                            nettyServerConfig.getServerChannelMaxIdleTimeSeconds()),
                        connectionManageHandler,
                        //serverHandler:处理读写请求
                        serverHandler
                    );
            }
        });

    addCustomConfig(serverBootstrap);

    try {
        ChannelFuture sync = serverBootstrap.bind().sync();
        InetSocketAddress addr = (InetSocketAddress) sync.channel().localAddress();
        if (0 == nettyServerConfig.getListenPort()) {
            this.nettyServerConfig.setListenPort(addr.getPort());
        }
        log.info("RemotingServer started, listening {}:{}", this.nettyServerConfig.getBindAddress(),
            this.nettyServerConfig.getListenPort());
        this.remotingServerTable.put(this.nettyServerConfig.getListenPort(), this);
    } catch (Exception e) {
        throw new IllegalStateException(String.format("Failed to bind to %s:%d", nettyServerConfig.getBindAddress(),
            nettyServerConfig.getListenPort()), e);
    }

    if (this.channelEventListener != null) {
        this.nettyEventExecutor.start();
    }

    this.timer.scheduleAtFixedRate(new TimerTask() {

        @Override
        public void run() {
            try {
                NettyRemotingServer.this.scanResponseTable();
            } catch (Throwable e) {
                log.error("scanResponseTable exception", e);
            }
        }
    }, 1000 * 3, 1000);
}

上面主要就是对netty绑定ip和端口, 注册四个handler,分别是编解码/心跳/处理连接请求/处理读写请求

三.总结

本文主要分析了NameServer的启动流程,整个启动流程分为3步:

  1. 创建controller:这一步主要是解析nameServer的配置并完成赋值操作

  2. 初始化controller:主要创建了NettyRemotingServer对象、netty服务线程池、定时任务

  3. 启动controller:就是启动netty 服务