rocketMQ之Name Server

1. rocketMQ总体结构图

RocketMQ 由四部分组成,举个栗子来说明下这些角色以及各自的功能。分布式mq是用来高效地传输消息的,它的功能跟以前在邮局中收发信件是一样的。邮政系统要正常运行,离不开下面这四个角色, 一是发信者,二是收信者,三是负责暂存和传输的邮局,四是负责协调邮局的机构。各个地方邮局的管理机构对应到 RocketMQ 中,就是 Producer、Consumer、Broker和NameServer

RocketMQ 各个角色间关系

2. Name Server的功能

NameServer 是整个消息队列中的状态服务器,类似于eureka和zookeeper等协调服务器,集群的各个组件通过它来了解全局的信息 同时午,各个角色的机器都要定期 NameServer 上报自己的状态,超时不上报的话, NameServer 就会认为某个机器掉线或出故障不可用了,就会把它从当前维护的机器列表中移除,并且在其他角色请求的时候返回,其他角色也会移除该记录的链接。
NameServer 可以部署多个,相互之间独立,其他角色同时向多个 NameServer机器上报状态信息,从而达到热备份的目的 NameServer 本身是无状态的,也就是说 NameServer 中的 Broker Topic等状态信息不会持久存储,都是由各个角色时上报并存储到内存中的,NameServer 也支持配置参数的持化, 但是一般没有必要这么做。

2.1 集群状态维护

NameServer维护了几个map进行状态topic以及broker组件的状态维护,下面来介绍下这几个map

  • topicQueueTable 这个结构的 Key 是 Topic 名称,它存储了所有 Topic 的属性信息。 Value 是个 QueueData 队列,队列的每一个元素代表一个master角色的broker, QueueData 里存储 Broker 读写 Queue 同步标识
  • BrokerAddrTable 这个map是以brokerName为key,相同名称的 Broker 可能存在多台机器, 一个Master 和多个 Slave, 这个map存储着一个 BrokerName 对应的属性信息,包括所属的 Cluster 名称,一个Master Broker 和多个 Slave Broker 的地址信息
  • ClusterAddrTable 这个map存储的是一个集群对应的所有的brokerName的列表。
  • BrokerLiveTable 这个map存的的key是broker对应的机器IP地址,BrokerAddrTable 跟它成为互补的关系;BrokerLiveTable存储的内容是对应机器的状态, 包括对应机器上次更新状态的时间戳,NameServer会定期检查这个时间戳,超时没有更新就认为这个Broker无效,将其从broker列表中清除。

2.2 集群状态维护

1 NameServer 作为一个服务协调者角色,客户端(broker)会实时上报节点状态,通过netty进行远程交互,每一个节点在NameServer维护一个channel,当channel断开时,会回调通知相应的service进行节点信息剔除;
2 通过一个单线程的定时任务对 BrokerLiveTable 进行周期性扫描,扫描间隔为10秒,主动剔除超过两分钟没有更新时间戳的broker机器信息。

3. 与协调的角色交互

NameServer作为一个协调组件,对应不同的客户端有着不同的交互方式

3.1 本地以及远端交互方式

1 NameServer的源码实现中,在启动的时候使用 Option 类定义了很多启动选项,比如-b为在指定的broker机器上创建topic, -c为在指定的集群下所有的master角色的broker创建topic。
2 对于producer和consumer,NameServer通过请求指令,为producer或consumer返回broker的机器信息。
3 对于broker,NameServer不仅要保持对broker的长链接,同时还要对其进行指令发送以及上下线管理。

3.2为何要自己实现NameServer

  阿里在rocketMQ之前,使用的是activeMQ进行系统消息流转,activeMQ是使用zookeeper作为集群的协调中间件,那为什么rocketMQ不继续使用zookeeper作为协调者呢?众所周知,zookeeper功能强大,并且被许许多多的分布式使用或者中间件当做协调者服务进行使用,它拥有永久节点、临时节点以及能够自动选举master的功能。
  也正是由于zookeeper功能太多强大,并且有一个很重要的点在于它是强一致性的,这也导致了它的可用性降低,会影响集群的可用性,境地性能。参照Zookeeper快速选举流程详解,可知zookeeper选举是一个多么复杂的过程,需要使用者投入很大的运维精力。而rocketMQ是一个追求稳定性的中间件,只要能够稳定地为client提供broker的链接即可,所以name server 的实现已经足够地简单,从而减少整体维护成本。

4. 通信协议

  RocketMQ自己定义了一个通信协议,使得模块间传输的二进制消息和有意义的内容之间互相转换。
在这里插入图片描述

  1. 第一部分是大端4个字节整数,值等于第二、三、四部分长度的总和;

  2. 第二部分是大端4个字节整数,值等于第三部分的长度;

  3. 第三部分是通过Json序列化的数据;

  4. 第四部分是通过应用自定义二进制序列化的数据。

5. 源码解析

rocketMQ的NameServer代码非常的少,阅读难度也相对低,下面来看一下它的源码结构以及执行的大体流程

Name Server源码结构
可以看到只有不到十个类,代码量非常的少!接下来我们从先预览一下它的只要流程

NamesrvStartup NamesrvController main() (1) createNamesrvController() (2) initialize() (3) start() (4) NamesrvStartup NamesrvController
  • (1) 方法启动

    public static void main(String[] args) {
        main0(args);
    }
    
    public static NamesrvController main0(String[] args) {
    
        try {
            NamesrvController controller = createNamesrvController(args);
            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;
    }
    
  • (2) 创建 NamesrvController

    • 初始化配置文件
    • 初始化日志文件
    • 创建对应的controller
    public static NamesrvController createNamesrvController(String[] args) throws IOException, JoranException {
        System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, Integer.toString(MQVersion.CURRENT_VERSION));
    
        Options options = ServerUtil.buildCommandlineOptions(new Options());
        commandLine = ServerUtil.parseCmdLine("mqnamesrv", args, buildCommandlineOptions(options), new PosixParser());
        if (null == commandLine) {
            System.exit(-1);
            return null;
        }
    
        final NamesrvConfig namesrvConfig = new NamesrvConfig();
        final NettyServerConfig nettyServerConfig = new NettyServerConfig();
        nettyServerConfig.setListenPort(9876);
        // -c命令行参数用来指定配置文件的位置
        if (commandLine.hasOption('c')) {
            //  省略非主要代码...
            }
        }
        // -p命令行参数用来打印所有配置 项的值。注意,用 -p参数打印配置项的值之后程序就退出了,这是一个帮助调 试的选项 。
        if (commandLine.hasOption('p')) {
            // ...省略非主要代码
        }
    
        // 把配置的属性文件转换为Java对象
        MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), namesrvConfig);
    
        // 启动的时候没有指定rocketmq根目录系统参数配置,启动中断
        if (null == namesrvConfig.getRocketmqHome()) {
            // 省略非主要代码...
        }
    
        // 从配置目录中读取日志文件
        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);
    
        final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig);
    
        // 保存所有的配置,防止配置丢失
        controller.getConfiguration().registerConfig(properties);
    
        return controller;
    }
    
  • (3) 初始化NamesrvController

    • 设置RemotingServer监听端口
    • 注册处理broker和client请求处理的processor
    • 启动定时任务清除掉线broker
    • 启动定时任务打印配置信息
    • 初始化监听ssl上下文配置文件是否被修改
// NamesrvController

public boolean initialize() {

        // 加载已经存在的配置
        this.kvConfigManager.load();

        // 初始化netty服务端
        this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);

        // 根据配置初始化自定义线程池
        this.remotingExecutor =
            Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), new ThreadFactoryImpl("RemotingExecutorThread_"));

        // 初始化请求处理器,这里是 DefaultRequestProcessor
        // 注册netty业务处理器
        this.registerProcessor();

        // 这里是开启一个线程不停地扫描已经掉线的broker,10秒一次
        this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {

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

        // 每隔10秒打印配置
        this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {

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

        if (TlsSystemConfig.tlsMode != TlsMode.DISABLED) {
            // 注册一个侦听器来重新加载SslContext
            try {
                // fileWatchService 中开启一个线程定时扫描这些证书文件(默认是没有证书文件的)是否有变化,如果有变化则重新加载
                fileWatchService = new FileWatchService(
                    new String[] {
                        TlsSystemConfig.tlsServerCertPath,
                        TlsSystemConfig.tlsServerKeyPath,
                        TlsSystemConfig.tlsServerTrustCertPath
                    },
                    new FileWatchService.Listener() {
                        boolean certChanged, keyChanged = false;
                        @Override
                        public void onChanged(String path) {
                            // 省略非主要代码...
                        }
                        private void reloadServerSslContext() {
                            ((NettyRemotingServer) remotingServer).loadSslContext();
                        }
                    });
            } catch (Exception e) {
                log.warn("FileWatchService created error, can't load the certificate dynamically");
            }
        }

        return true;
    }

  • (4) NamesrvController 启动
// NamesrvController
public void start() throws Exception {
        // 启动netty服务端程序
        this.remotingServer.start();

        // 启动证书文件扫描的service
        if (this.fileWatchService != null) {
            this.fileWatchService.start();
        }
    }

* 启动netty服务端程序
* 启动证书文件扫描的service

小结

本篇介绍了rocketMQ的中NameServer的工作方式以及为何独立编写它的原因,并且配合源码介绍了它的功能实现。
本篇仅仅从个人角度对个人的学习的一个总结,里面还有很多不足的地方,比如没有很好的介绍远程传输协议部分,这个后面找时间补上,以及其他一些不足的地方,希望各位看官大神指出,一定找时间修正,输出更好的文章对本人来说也是一个进步。

本人fork下来的源码,里面在看过的地方都加上了个人理解的注释代码地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值