RocketMQ4.3.X笔记(11):NameServer 源码解析

源码目录

  1. 整个功能很简单,一共就 8 个类
  • KVConfigManager
  • KVConfigSerializeWrapper
  • ClusterTestRequestProcessor
  • DefaultRequestProcessor
  • BrokerHousekeepingService
  • RouteInfoManager.java
  • NamesrvController
  • NamesrvStartup
  1. 依赖核心模块:rocketmq-remoting

NameServer 模块

模块入口代码的功能 NamesrvStartup

入口函数:

  1. NamesrvStartupNameServer模块的启动入口, NamesrvController 是用来协块各个调模功能的代码
  2. NamesrvStartup.java 类的入口函数main,发现把逻辑转到main0() 函数
  3. main0()函数的主要功能:
  • 是解析命令行参数,重点是解析-c 和-p 参数
  • 初始化NameServerController,即初始化了 NamesrvController.java

解析命令行参数

  1. 解析命令行参数源码
# 解析命令行参数
Options options = ServerUtil.buildCommandlineOptions(new Options());
commandLine = ServerUtil.parseCmdLine("mqnamesrv", args, buildCommandlineOptions(options), new PosixParser());
if (null == commandLine) {
    System.exit(-1);
    return null;
}

# 判断参数 -c
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);
}
  1. 简单分析
  • -c 命令行参数用来指定配置文件的位置
  • -p 命令行参数用来打印所有配置项的值
  • 注意,用-p 参数打印配置项的值之后程序就退出了,这是一个帮助调试的选项

初始化NameServer 的Controller

  1. main0 函数的另外一个功能是初始化 NamesrvController,源码如下
  • 根据解析出的配置参数, 调用controller.initialize()来初始化,然后调用controller.start()NameServer 开始服务
  • 还有一个逻辑是注册 ShutdownHookThread,当程序退出的时候会调用controller.shutdown 来做退出前的清理工作
# createNamesrvController() 方法中,在解析参数后面
# 解析出配置参数
final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig);

// remember all configs to prevent discard
controller.getConfiguration().registerConfig(properties);

# start(controller) 方法中
public static NamesrvController start(final NamesrvController controller) throws Exception {

    if (null == controller) {
        throw new IllegalArgumentException("NamesrvController is null");
    }
    # 初始化
    boolean initResult = controller.initialize();
    if (!initResult) {
        controller.shutdown();
        System.exit(-3);
    }

    # 注册 `ShutdownHookThread`, 当程序退出的时候会调用`controller.shutdown` 来做退出前的清理工作
    Runtime.getRuntime().addShutdownHook(new ShutdownHookThread(log, new Callable<Void>() {
        @Override
        public Void call() throws Exception {
            controller.shutdown();
            return null;
        }
    }));
    # 启动开始服务
    controller.start();

    return controller;
}

NameServer 的总控逻辑

NameServer 的总控逻辑在NamesrvController.java 代码中。NameServer 是集群的协调者,它只是简单地接收其他角色报上来的状态,然后根据请求返回相应的状态

初始化执行线程池

  1. NameserverController.initialize() 函数完成,源码如下:
  2. 一共3个线程池
  • 启动了一个默认是8 个线程的线程池
  • 1个定时线程池,负责扫描失效的Broker(scanNotActiveBroker)
  • 1个定时线程池,打印配置信息(printAllPeriodically)
# 启动了一个默认是8 个线程的线程池
this.remotingExecutor =
    Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), new ThreadFactoryImpl("RemotingExecutorThread_"));

# 定时线程池,负责扫描失效的Broker(scanNotActiveBroker)
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {
        NamesrvController.this.routeInfoManager.scanNotActiveBroker();
    }
}, 5, 10, TimeUnit.SECONDS);

# 定时线程池,打印配置信息(printAllPeriodically)
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {
        NamesrvController.this.kvConfigManager.printAllPeriodically();
    }
}, 1, 10, TimeUnit.MINUTES);

启动通信服务

  1. 启动负责通信的服务 remotingServer
  2. remotingServer 监昕一些端口,收到BrokerClient 等发过来的请求后,根据请求的命令,调用不同的Processor来处理。这些不同的处理逻辑被放到上面初始化的线程池中执行
  3. remotingServer 是基于Netty 封装的一个网络通信服务
# NameserverController.initialize() 方法中
# 初始化 remotingServer
this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);

# 注册 Processor
this.registerProcessor();
private void registerProcessor() {
    if (namesrvConfig.isClusterTest()) {
        this.remotingServer.registerDefaultProcessor(new ClusterTestRequestProcessor(this, namesrvConfig.getProductEnvName()),
            this.remotingExecutor);
    } else {
        this.remotingServer.registerDefaultProcessor(new DefaultRequestProcessor(this), this.remotingExecutor);
    }
}

核心业务逻辑处理

  1. NameServer 的核心业务逻辑, 在DefaultRequestProcessor.javaprocessRequest方法中 中,网络通信服务模块收到请求后,就调用这个Processor 来处理
    2.逻辑主体是个switch 语句,根据RequestCode 调用不同的函数来处理,从RequestCode 可以了解到Name Server 的主要功能,比如:
  • REGISTER_BROKER 是在集群中新加入一个Broker 机器
  • GET_ROUTEINTO_BY_TOPIC 是请求获取一个Topic 的路由信息
  • WIPE_WRITE_PERM_OF_BROKER 是删除一个Broker 的写权限
switch (request.getCode()) {
    case RequestCode.PUT_KV_CONFIG:
        return this.putKVConfig(ctx, request);
    case RequestCode.GET_KV_CONFIG:
        return this.getKVConfig(ctx, request);
    case RequestCode.DELETE_KV_CONFIG:
        return this.deleteKVConfig(ctx, request);
    case RequestCode.QUERY_DATA_VERSION:
        return queryBrokerTopicConfig(ctx, request);
    case RequestCode.REGISTER_BROKER:
        Version brokerVersion = MQVersion.value2Version(request.getVersion());
        if (brokerVersion.ordinal() >= MQVersion.Version.V3_0_11.ordinal()) {
            return this.registerBrokerWithFilterServer(ctx, request);
        } else {
            return this.registerBroker(ctx, request);
        }
    case RequestCode.UNREGISTER_BROKER:
        return this.unregisterBroker(ctx, request);
    case RequestCode.GET_ROUTEINTO_BY_TOPIC:
        return this.getRouteInfoByTopic(ctx, request);
    case RequestCode.GET_BROKER_CLUSTER_INFO:
        return this.getBrokerClusterInfo(ctx, request);
    case RequestCode.WIPE_WRITE_PERM_OF_BROKER:
        return this.wipeWritePermOfBroker(ctx, request);
    case RequestCode.GET_ALL_TOPIC_LIST_FROM_NAMESERVER:
        return getAllTopicListFromNameserver(ctx, request);
    case RequestCode.DELETE_TOPIC_IN_NAMESRV:
        return deleteTopicInNamesrv(ctx, request);
    case RequestCode.GET_KVLIST_BY_NAMESPACE:
        return this.getKVListByNamespace(ctx, request);
    case RequestCode.GET_TOPICS_BY_CLUSTER:
        return this.getTopicsByCluster(ctx, request);
    case RequestCode.GET_SYSTEM_TOPIC_LIST_FROM_NS:
        return this.getSystemTopicListFromNs(ctx, request);
    case RequestCode.GET_UNIT_TOPIC_LIST:
        return this.getUnitTopicList(ctx, request);
    case RequestCode.GET_HAS_UNIT_SUB_TOPIC_LIST:
        return this.getHasUnitSubTopicList(ctx, request);
    case RequestCode.GET_HAS_UNIT_SUB_UNUNIT_TOPIC_LIST:
        return this.getHasUnitSubUnUnitTopicList(ctx, request);
    case RequestCode.UPDATE_NAMESRV_CONFIG:
        return this.updateConfig(ctx, request);
    case RequestCode.GET_NAMESRV_CONFIG:
        return this.getConfig(ctx, request);
    default:
        break;
}

集群状态存储

具体结构

  1. NameServer 作为集群的协调者,需要保存和维护集群的各种元数据, 这是通过RoutelnfoManager 类来实现的
  2. 每个结构存储着一类集群信息
属性描述
HashMap<String/* topic */, List> topicQueueTabletopicQueueTable 这个结构的Key 是Topic 的名称,它存储了所有Topic的属性信息。Value 是个QueueData 队列, 队里的长度等于这个Topic数据存储的Master Broker 的个数, QueueData 里存储着Broker 的名称、读写queue 的数量、同步标识等
HashMap<String/* brokerName */, BrokerData> brokerAddrTable;BrokerAddrTable以BrokerName 为索引,相同名称的Broker 可能存在多台机器, 一个Master 和多个Slave 。这个结构存储着一个BrokerName 对应的属性信息,包括所属的Cluster 名称, 一个Master Broker 和多个Slave Broker的地址信息
HashMap<String/* clusterName /, Set<String/ brokerName */>> clusterAddrTable;ClusterAddrTable存储的是集群中C luster 的信息,结果很简单,就是一个Cluster 名称对应一个由BrokerName 组成的集合
HashMap<String/* brokerAddr */, BrokerLiveInfo> brokerLiveTable;这个结构和BrokerAddrTable 有关系,但是内容完全不同,这个结构的Key 是BrokerAddr ,也就是对应着一台机器, BrokerAddrTable 中的Key是BrokerName , 多个机器的BrokerName 可以相同。BrokerLiveTable存储的内容是这台Broker 机器的实时状态,包括上次更新状态的时间戳, NameServer 会定期检查这个时间戳,超时没有更新就认为这个Broker 无效了,将其从Broker 列表里清除
HashMap<String/* brokerAddr /, List/ Filter Server */> filterServerTable;Filter Server 是过滤服务器,是RocketMQ 的一种服务端过滤方式,一个Broker 可以有一个或多个F ilter Server 。这个结构的Key 是Broker的地址, Value 是和这个Broker 关联的多个Filter Server 的地址
  1. 源码如下
private final HashMap<String/* topic */, List<QueueData>> topicQueueTable;
private final HashMap<String/* brokerName */, BrokerData> brokerAddrTable;
private final HashMap<String/* clusterName */, Set<String/* brokerName */>> clusterAddrTable;
private final HashMap<String/* brokerAddr */, BrokerLiveInfo> brokerLiveTable;
private final HashMap<String/* brokerAddr */, List<String>/* Filter Server */> filterServerTable;

public RouteInfoManager() {
    this.topicQueueTable = new HashMap<String, List<QueueData>>(1024);
    this.brokerAddrTable = new HashMap<String, BrokerData>(128);
    this.clusterAddrTable = new HashMap<String, Set<String>>(32);
    this.brokerLiveTable = new HashMap<String, BrokerLiveInfo>(256);
    this.filterServerTable = new HashMap<String, List<String>>(256);
}

控制访问这些结构的锁机制

  1. NameServer的场景中,读取操作多,更改操作少(读多写少),所以选择读写锁能大大提高效率
private final ReadWriteLock lock = new ReentrantReadWriteLock();
  1. RoutelnfoManager 中使用的是可重人的读写锁,以deleteTopic 函数为例:
public void deleteTopic(final String topic) {
    try {
        try {
            this.lock.writeLock().lockInterruptibly();
            this.topicQueueTable.remove(topic);
        } finally {
            this.lock.writeLock().unlock();
        }
    } catch (Exception e) {
        log.error("deleteTopic Exception", e);
    }
}

事件监听 BrokerHousekeepingService

  1. 继承 ChannelEventListener类,主要负责连接断开的回调与监听
  2. 监听的事件如:onChannelConnectonChannelCloseonChannelExceptiononChannelIdle
  3. 在监听的回调方法中,会调用 NamesrvControllerRouteInfoManager中的方法,更新状态信息等
@Override
public void onChannelClose(String remoteAddr, Channel channel) {
    this.namesrvController.getRouteInfoManager().onChannelDestroy(remoteAddr, channel);
}

@Override
public void onChannelException(String remoteAddr, Channel channel) {
    this.namesrvController.getRouteInfoManager().onChannelDestroy(remoteAddr, channel);
}

@Override
public void onChannelIdle(String remoteAddr, Channel channel) {
    this.namesrvController.getRouteInfoManager().onChannelDestroy(remoteAddr, channel);
}

参考

源码地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值