Name Server元数据架构与源码分析

185 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第10天,点击查看活动详情

前言

image.png NameServer注册中心类似kafka的注册中zookeeper。

1、NameServer

NameServer是一个Broker和Topic路由的注册中心,支持Broker的动态注册和发现

主要包括两个功能
1、Broker管理,NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制,检查Broker是否还存活;

2、路由信息管理,每个NameServer将保存关于Broker集群的整个路由信息和用于客户端查询的队列信息。然后Producer和Conumser通过NameServer就可以知道整个Broker集群的路由信息,从而进行消息的投递和消费。NameServer通常也是集群的方式部署,各实例间相互不进行信息通讯。Broker是向每一台NameServer注册自己的路由信息,所以每一个NameServer实例上面都保存一份完整的路由信息。当某个NameServer因某种原因下线了,Broker仍然可以向其它NameServer同步其路由信息,Producer和Consumer仍然可以动态感知Broker的路由的信息。

3、NameServer无状态方式有什么优缺点?

优点:NameServer集群搭建简单
缺点:对于Broker,必须明确指出所有NameServer地址,否则未指出的将不会去注册。NameServer不能随便扩容

1.1、NameServer启动流程

image.png

public static NamesrvController main0(String[] args) {

    try {
        //1、 首先来解析配置文件,需要填充NameServerConfig、NettyServerConfig属性值。
        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;
}


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

    if (null == controller) {
        throw new IllegalArgumentException("NamesrvController is null");
    }
    // 2、根据启动属性创建NamesrvController实例,并初始化该实例,nameServercontroller实例为NameServer核心控制器。
    boolean initResult = controller.initialize();
    if (!initResult) {
        controller.shutdown();
        System.exit(-3);
    }

    // 3、注册JVM钩子函数并启动服务器,以便监听Broker、消息生产者的网络请求
    Runtime.getRuntime().addShutdownHook(new ShutdownHookThread(log, new Callable<Void>() {
        @Override
        public Void call() throws Exception {
            controller.shutdown();
            return null;
        }
    }));

    controller.start();

    return controller;
}

1、 创建NameServerConfig、NettyServerConfig实例,然后再解析启动时的参数,读取配置文件,将属性填充到namesevConfig、nettyServerConfig对象中。

public class NamesrvConfig {
    // 主目录
    private String rocketmqHome = System.getProperty(MixAll.ROCKETMQ_HOME_PROPERTY, System.getenv(MixAll.ROCKETMQ_HOME_ENV));
    // 存储kv配置属性的持久化路径
    private String kvConfigPath = System.getProperty("user.home") + File.separator + "namesrv" + File.separator + "kvConfig.json";
    // 默认配置文件路径
    private String configStorePath = System.getProperty("user.home") + File.separator + "namesrv" + File.separator + "namesrv.properties";
    private String productEnvName = "center";
    private boolean clusterTest = false;
    // 是否支持顺序消息
    private boolean orderMessageEnable = false;
public class NettyServerConfig implements Cloneable {
    // 监听端口,该值会被初始化为9876
    private int listenPort = 8888;
    // 业务线程池数
    private int serverWorkerThreads = 8;
    // 回调线程数
    private int serverCallbackExecutorThreads = 0;
    // IO线程池数
    private int serverSelectorThreads = 3;
    // send oneway消息请求并发度
    private int serverOnewaySemaphoreValue = 256;
    // 异步消息发送最大并发度
    private int serverAsyncSemaphoreValue = 64;
    // 网络连接最大空闲时间,默认120秒
    private int serverChannelMaxIdleTimeSeconds = 120;

    private int serverSocketSndBufSize = NettySystemConfig.socketSndbufSize;
    private int serverSocketRcvBufSize = NettySystemConfig.socketRcvbufSize;
    private int writeBufferHighWaterMark = NettySystemConfig.writeBufferHighWaterMark;
    private int writeBufferLowWaterMark = NettySystemConfig.writeBufferLowWaterMark;
    private int serverSocketBacklog = NettySystemConfig.socketBacklog;
    private boolean serverPooledByteBufAllocatorEnable = true;

2、根据启动属性创建NamesrvController实例,并初始化该实例,nameServercontroller实例为NameServer核心控制器。这一步主要看controller.initialize();函数

public boolean initialize() {
    // 1、加载kv配置
    this.kvConfigManager.load();
    
    //2、创建nettyServer网络处理对象
    this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);
    // 3、线程池
    this.remotingExecutor =
        Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), new ThreadFactoryImpl("RemotingExecutorThread_"));
    this.registerProcessor();
    
    // 定时任务1, nameServer每隔10s扫描一次broker,移除处于不激活状态的Broker。
    this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {

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

    // 定时任务2,nameServer每隔10分钟打印一次KV配置。
    this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {

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

    if (TlsSystemConfig.tlsMode != TlsMode.DISABLED) {
        // Register a listener to reload SslContext
        try {
            fileWatchService = new FileWatchService(
                new String[] {
                    TlsSystemConfig.tlsServerCertPath,
                    TlsSystemConfig.tlsServerKeyPath,
                    TlsSystemConfig.tlsServerTrustCertPath
                },
                new FileWatchService.Listener() {
                    boolean certChanged, keyChanged = false;
                    @Override
                    public void onChanged(String path) {
                        if (path.equals(TlsSystemConfig.tlsServerTrustCertPath)) {
                            log.info("The trust certificate changed, reload the ssl context");
                            reloadServerSslContext();
                        }
                        if (path.equals(TlsSystemConfig.tlsServerCertPath)) {
                            certChanged = true;
                        }
                        if (path.equals(TlsSystemConfig.tlsServerKeyPath)) {
                            keyChanged = true;
                        }
                        if (certChanged && keyChanged) {
                            log.info("The certificate and private key changed, reload the ssl context");
                            certChanged = keyChanged = false;
                            reloadServerSslContext();
                        }
                    }
                    private void reloadServerSslContext() {
                        ((NettyRemotingServer) remotingServer).loadSslContext();
                    }
                });
        } catch (Exception e) {
            log.warn("FileWatchService created error, can't load the certificate dynamically");
        }
    }

    return true;
}

3、 注册JVM钩子函数并启动服务器,开始监听Broker、消息生产者的网络请求。

1.2、元数据注册流程源码分析

元数据注册两种场景

1、在Broker节点在启动的时候,轮询NameServer列表, Broker与每个NameServer节点建立长连接,发起注册请求。
2、Broker节点为了证明自己是存活的,会将最新的信息上报给NameServer,然后每隔30秒向 NameServer发送心跳包。

1.3 路由剔除

1.4 路由发现