RocketMQ Namesrv

RocketMQ 技术架构

  整体由几大部分组成:生产者、消费者、namesrv、broker、zookeeper

  • 生产端

    生产端集群化部署,通过Namesrv进行路由,将消息发送到broker。

  • 消费端

    消费端集群化部署,通过Namesrv进行路由,从broker拉取消息进行消费。

  • broker

    消息中转角色,负责存储消息,转发消息。

    提供高并发读写服务,进行负载均衡与动态伸缩。

  • namesrv

    轻量级注册中心,无状态节点,提供命名服务,负责更新和发现 Broker 服务。生产者和消费者就可以从Name Server中定时(30秒)获取相关Broker信息。

    多个Namesrv之间相互没有通信,单台Namesrv宕机不影响其他Namesrv节点与集群的功能;即使整个Namesrv集群宕机,已经正常工作的Producer,Consumer,Broker仍然能正常工作,但新起的Producer, Consumer,Broker就无法工作。

  • zookeeper

    引入zookeeper进行failover与元数据管理。

    namesrv与broker监听zk节点变化,通过zk节点的变化协调主备之间的角色和转换。

    对元数据进行集中式管理,包括主题信息、订阅组信息、消费进度等。一方面可以避免元数据的同步带来的复杂性和一致性问题,另一方面方便动态扩容。

namesrv存在意义

  RocketMQ的注册中心nameSrv在rocketMQ中相当于zookeeper在dubbo中的作用,是用来存储broke和client的注册信息的。broke和clicent在启动的时候都会去连接nameSrv获取信息

  在RocketMQ网络部署图中,broker相当于服务端,而Producer、Consumer都是相当于其客户端,如果broker固定死永远不变,那么namesrv存在就没有任何一样的,但是由于服务端自动伸缩、故障以及升级等,服务端会变动,因此namesrv就有存在的意义了

  因此需要一个类似namesrv的东西存在,一般存在两种机制:客户端发现机制和服务端发现机制
    1 客户端发现机制:当发出请求服务时,客户端通过注册中心服务知道所有的服务实例。客户端接着使用负载均衡算法选择可用的服务实例中的一个并进行发送。
    2 服务端发现机制:发出请求服务时,客户端通过请求负载平衡器,负载均衡器通过注册中心服务知道所有的服务实例。负载均衡器接着使用负载均衡算法选择可用的服务实例中的一个并进行发送
  备注: Nginx HTTP服务器和反向代理服务器就是这种

  两种机制总结:
    1 客户端发现机制:客户端有所有可用的服务实例,可以灵活方便的特定应用进行特定的负载均衡决策
    2 服务端发现机制:客户端只需要给负载均衡器发请求即可,客户端屏蔽掉了一些细节

namesrv启动流程

  nameSrv启动入口

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

public static NamesrvController createNamesrvController(String[] args) throws IOException, JoranException {

    // 根据命令行参数,使用commons-cli命令行工具包解析生成CommandLine对象
    // 在parseCmdLine中,如果命令行中有-h选项,执行打印帮助文档的逻辑,然后退出,不再继续
    Options options = ServerUtil.buildCommandlineOptions(new Options());
    commandLine = ServerUtil.parseCmdLine("mqnamesrv", args, buildCommandlineOptions(options), new PosixParser());
    if (null == commandLine) {
        System.exit(-1);
        return null;
    }

    // 初始化了2个配置 NamesrvConfig,NettyServerConfig,其中NettyServerConfig监听9876是硬编码的
    // 然后通过命令行参数 -c 指定一个配置文件,然后将配置文件中的内容解析成NamesrvConfig,NettyServerConfig的配置
    // 设置NamesrvConfig,NettyServerConfig的逻辑是看类中的set方法,如果set方法后的名字和配置文件中的key匹配,就会设置对应的值
    final NamesrvConfig namesrvConfig = new NamesrvConfig();
    final NettyServerConfig nettyServerConfig = new NettyServerConfig();
    nettyServerConfig.setListenPort(9876);
    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 选项,会在控制台打印配置信息,然后退出,不再继续执行
    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
    // 在NamesrvConfig中,可以看到使用系统属性rocketmq.home.dir,环境变量ROCKETMQ_HOME和前面的-c指定的配置文件设置RocketMQHome
    // 在mqnamesrv启动脚本中会自定探测RockerMQ并export 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);
    }

    // 加载logback.xml
    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);

    // 使用logback打印NamesrvConfig,NettyServerConfig配置信息
    MixAll.printObjectProperties(log, namesrvConfig);
    MixAll.printObjectProperties(log, nettyServerConfig);

    final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig);

    // 最后还把-c指定的文件的配置在保存到Configruation中
    controller.getConfiguration().registerConfig(properties);

    return controller;
}

  可以看到NamesrvStartup只是一个启动类,主要逻辑都在处理命令行和配置,主要功能都是在NamesrvController中,而且我们可以看到,在处理配置的时候,真的是对配置文件进行反复处理
  首先通过-c指定配置文件,使用MixAll.properties2Object将配置设置到NamesrvConfig,NettyServerConfig

MixAll.properties2Object(properties, namesrvConfig);
MixAll.properties2Object(properties, nettyServerConfig);

  然后通过命令行参数设置到NamesrvConfig

MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), namesrvConfig);
  然后初始化NamesrvController,NamesrvController中会初始化一个Configuration类,Configuration类中又会把NamesrvConfig,NettyServerConfig都merge到allConfigs中
public NamesrvController(NamesrvConfig namesrvConfig, NettyServerConfig nettyServerConfig) {
    this.namesrvConfig = namesrvConfig;
    this.nettyServerConfig = nettyServerConfig;
    this.kvConfigManager = new KVConfigManager(this);
    this.routeInfoManager = new RouteInfoManager();
    this.brokerHousekeepingService = new BrokerHousekeepingService(this);
    // 初始化Configuration
    this.configuration = new Configuration(
        log,
        this.namesrvConfig, this.nettyServerConfig
    );
    this.configuration.setStorePathFromConfig(this.namesrvConfig, "configStorePath");
}
public Configuration(InternalLogger log, Object... configObjects) {
    this.log = log;
    if (configObjects == null || configObjects.length == 0) {
        return;
    }
    // 将NamesrvConfig,NettyServerConfig注册
    for (Object configObject : configObjects) {
        registerConfig(configObject);
    }
}
public Configuration registerConfig(Object configObject) {
    try {
        readWriteLock.writeLock().lockInterruptibly();

        try {

            Properties registerProps = MixAll.object2Properties(configObject);
                        // 将NamesrvConfig,NettyServerConfig合并到allConfigs
            merge(registerProps, this.allConfigs);

            configObjectList.add(configObject);
        } finally {
            readWriteLock.writeLock().unlock();
        }
    } catch (InterruptedException e) {
        log.error("registerConfig lock error");
    }
    return this;
}

  最后还要把-c指定的配置文件和allConfigs进行合并

// 最后还把-c指定的文件的配置在保存到Configruation中
controller.getConfiguration().registerConfig(properties);

  可以看到-c指定的配置文件读取进来后被拆分为NamesrvConfig,NettyServerConfig,然后又和Configuration中的allConfigs合并,最后还要再合并一次。NamesrvController初始化完成后,就调用start(controller),才真正的开始

// 启动NamesrvController
start(controller);

// 在start(controller)方法中最关键的就是下面2个方法

controller.initialize();
controller.start();

  NamesrvController初始化

public boolean initialize() {
    
    // 从NamesrvConfig#KvConfigPath指定的文件中反序列化数据到KVConfigManager#configTable中
    this.kvConfigManager.load();
    // 启动网络通信的Netty服务
    this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);

    // 初始化一下负责处理Netty网络交互数据的线程池,
    this.remotingExecutor =
        Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), new ThreadFactoryImpl("RemotingExecutorThread_"));

    // 注册一个默认负责处理Netty网络交互数据的DefaultRequestProcessor,这个Processor会使用remotingExecutor执行
    // *划重点,后面这里会再次提到*
    this.registerProcessor();

    // 每10s扫描一下失效的Broker
    this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {

        @Override
        public void run() {
            NamesrvController.this.routeInfoManager.scanNotActiveBroker();
        }
    }, 5, 10, TimeUnit.SECONDS);

    // 每10min打印一下前面被反复蹂躏的配置
    this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {

        @Override
        public void run() {
            NamesrvController.this.kvConfigManager.printAllPeriodically();
        }
    }, 1, 10, TimeUnit.MINUTES);


    // 设置TLS,这块不太了解,所以省略了,以后用空了再研究一下TLS吧
    if (TlsSystemConfig.tlsMode != TlsMode.DISABLED) {
       ...
    }

    return true;
}

  启动NamesrvController

public void start() throws Exception {
    this.remotingServer.start();

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

  可以看到逻辑还算比较清晰,关键功能在KVConfigManager,RouteInfoManager和NettyRemotingServer实现

  我们先来看看NettyRemotingServer

public NettyRemotingServer(final NettyServerConfig nettyServerConfig,
    final ChannelEventListener channelEventListener) {
    // 初始化2个Semaphore,一个是one-way请求的并发数,一个是asynchronous请求的并发数,可以简单理解成对2种请求做了限流,至于什么是one-way请求,什么是asynchronous请求,分析到了再说吧
    super(nettyServerConfig.getServerOnewaySemaphoreValue(), nettyServerConfig.getServerAsyncSemaphoreValue());
    this.serverBootstrap = new ServerBootstrap();
    this.nettyServerConfig = nettyServerConfig;
    this.channelEventListener = channelEventListener;

    int publicThreadNums = nettyServerConfig.getServerCallbackExecutorThreads();
    if (publicThreadNums <= 0) {
        publicThreadNums = 4;
    }
        
     // 初始化一个公用的线程池,什么情况下用这个公用的线程池?看后面的分析
    this.publicExecutor = Executors.newFixedThreadPool(publicThreadNums, new ThreadFactory() {
        private AtomicInteger threadIndex = new AtomicInteger(0);

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

    // 下面这个就是判断系统是否支持epoll来初始化相应的EventLoopGroup,如果不是很了解Netty的同学可以先去学学Netty
    if (useEpoll()) {
        this.eventLoopGroupBoss = new EpollEventLoopGroup();

        this.eventLoopGroupSelector = new EpollEventLoopGroup();
    } else {
        this.eventLoopGroupBoss = new NioEventLoopGroup();

        this.eventLoopGroupSelector = new NioEventLoopGroup();
    }
        // 这个也是TLS相关的忽略分析
    loadSslContext();
}

  这里可以看一下怎么判断系统是否支持epoll的

private static boolean isLinuxPlatform = false;
private static boolean isWindowsPlatform = false;

static {
    if (OS_NAME != null && OS_NAME.toLowerCase().contains("linux")) {
        isLinuxPlatform = true;
    }

    if (OS_NAME != null && OS_NAME.toLowerCase().contains("windows")) {
        isWindowsPlatform = true;
    }
}

-----

private boolean useEpoll() {
    return RemotingUtil.isLinuxPlatform()
        && nettyServerConfig.isUseEpollNativeSelector()
        && Epoll.isAvailable();
}
  还记得在NamesrvController初始化时注册一个默认负责处理Netty网络交互数据的DefaultRequestProcessor,DefaultRequestProcessor使用了一个专用的remotingExecutor线程池,我们也可以注册其他的Processor,如果我们注册Processor时没有指定线程池就会使用公共的线程池publicExecutor
 public void registerProcessor(int requestCode, NettyRequestProcessor processor, ExecutorService executor) {
    ExecutorService executorThis = executor;
    if (null == executor) {
        executorThis = this.publicExecutor;
    }

    Pair<NettyRequestProcessor, ExecutorService> pair = new Pair<NettyRequestProcessor, ExecutorService>(processor, executorThis);
    this.processorTable.put(requestCode, pair);
}

  上面只是将Netty的EventLoopGroup进行了初始化,却没有真正的启动Netty,真正的启动还得调用remotingServer.start();

public void start() throws Exception {
    this.remotingServer.start();
        // fileWatchService和TLS有关,大概就是会监听TLS相关文件的改变,也不仔细分析了
    if (this.fileWatchService != null) {
        this.fileWatchService.start();
    }
}

  接下来看看NettyRemotingServer的start()方法

public void start() {
    // 初始化一个线程池,用于执行共享的Handler
    this.defaultEventExecutorGroup = new DefaultEventExecutorGroup(
        nettyServerConfig.getServerWorkerThreads(),
        new ThreadFactory() {

            private AtomicInteger threadIndex = new AtomicInteger(0);

            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "NettyServerCodecThread_" + this.threadIndex.incrementAndGet());
            }
        });
        // 初始化一些共享的Handler,HandshakeHandler,NettyEncoder,NettyConnectManageHandler,NettyServerHandler
    prepareSharableHandlers();
        // 后面就是一些Netty的设置,具体看Netty
    ServerBootstrap childHandler =
        this.serverBootstrap.group(this.eventLoopGroupBoss, this.eventLoopGroupSelector)
            .channel(useEpoll() ? EpollServerSocketChannel.class : NioServerSocketChannel.class)
            .option(ChannelOption.SO_BACKLOG, 1024)
            .option(ChannelOption.SO_REUSEADDR, true)
            .option(ChannelOption.SO_KEEPALIVE, false)
            .childOption(ChannelOption.TCP_NODELAY, true)
            .childOption(ChannelOption.SO_SNDBUF, nettyServerConfig.getServerSocketSndBufSize())
            .childOption(ChannelOption.SO_RCVBUF, nettyServerConfig.getServerSocketRcvBufSize())
            .localAddress(new InetSocketAddress(this.nettyServerConfig.getListenPort()))
                // 我们只需要关心这里设置了哪些handler
            .childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline()
                        .addLast(defaultEventExecutorGroup, HANDSHAKE_HANDLER_NAME, handshakeHandler)
                        .addLast(defaultEventExecutorGroup,
                            encoder,
                            new NettyDecoder(),
                            new IdleStateHandler(0, 0,          nettyServerConfig.getServerChannelMaxIdleTimeSeconds()),
                            connectionManageHandler,
                            serverHandler
                        );
                }
            });

    if (nettyServerConfig.isServerPooledByteBufAllocatorEnable()) {
        childHandler.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
    }

    try {
        ChannelFuture sync = this.serverBootstrap.bind().sync();
        InetSocketAddress addr = (InetSocketAddress) sync.channel().localAddress();
        this.port = addr.getPort();
    } catch (InterruptedException e1) {
        throw new RuntimeException("this.serverBootstrap.bind().sync() InterruptedException", e1);
    }
        // 这里有一个channelEventListener,在NamesrvController中channelEventListener就是BrokerHousekeepingService,BrokerHousekeepingService负责在broker断开连接的时候,移除RouteInfoManager中的路由信息
    // NettyEventExecutor会维护一个NettyEvent的队列,NettyConnectManageHandler会向NettyEvent的队列中添加Event,然后由channelEventListener进行消费
    if (this.channelEventListener != null) {
        this.nettyEventExecutor.start();
    }
        // 定时扫描responseTable,执行超时请求的callback
    // 这里有2个疑问,是谁向responseTable中put数据?为什么这里只执行超时请求的callback,正常结束的请求在哪处理的?
    this.timer.scheduleAtFixedRate(new TimerTask() {

        @Override
        public void run() {
            try {
                NettyRemotingServer.this.scanResponseTable();
            } catch (Throwable e) {
                log.error("scanResponseTable exception", e);
            }
        }
    }, 1000 * 3, 1000);
}
 
 
参考:https://blog.csdn.net/lirenzuo/article/details/79875409 匠心零度
   https://www.jianshu.com/p/fbbce22b7c92  懒癌正患者
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

posted on 2022-01-07 16:33  胡子就不刮  阅读(606)  评论(0编辑  收藏  举报

导航