RocketMQ学习随笔(2)-Namesrv启动及路由

Namesrv启动

1.启动入口

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

public static NamesrvController main0(String[] args) {

    try {
        //生成NamesrvController对象
        NamesrvController controller = createNamesrvController(args);
        //调用start方法,传入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;
}

2.配置文件的解析以及加载

public static NamesrvController createNamesrvController(String[] args) throws IOException, JoranException {
        //设置系统变量 版本号
        System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, Integer.toString(MQVersion.CURRENT_VERSION));
        //PackageConflictDetect.detectFastjson();
        //namesrv命令参数 注册以及解析
        Options options = ServerUtil.buildCommandlineOptions(new Options());
        commandLine = ServerUtil.parseCmdLine("mqnamesrv", args, buildCommandlineOptions(options), new PosixParser());
        if (null == commandLine) {
            System.exit(-1);
            return null;
        }

        //初始化NamesrvConfig类
        final NamesrvConfig namesrvConfig = new NamesrvConfig();
        //初始化NettyServerConfig类
        final NettyServerConfig nettyServerConfig = new NettyServerConfig();
        //设置监听端口9876
        nettyServerConfig.setListenPort(9876);
        //通过 -c 参数指定配置文件
        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);
        }
        // 通过"--具体属性名 属性值"指定属性值
        MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), namesrvConfig);

        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);
        }
		
        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);

        //根据NamesrvConfig和NettyServerConfig生成NamesrvController
        final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig);

        // remember all configs to prevent discard 保存所有的配置
        controller.getConfiguration().registerConfig(properties);

        return controller;
    }

首先加载配置文件,主要在于生成namesrvConfignettyConfig两个配置类对象.namesrvConfig主要包含了配置文件的路径,nettyConfig则包含了netty启动时的参数:

public class NamesrvConfig {
    private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);

    private String rocketmqHome = System.getProperty(MixAll.ROCKETMQ_HOME_PROPERTY, System.getenv(MixAll.ROCKETMQ_HOME_ENV));
    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";
...
}
public class NettyServerConfig implements Cloneable {
    private int listenPort = 8888;
    private int serverWorkerThreads = 8;
    private int serverCallbackExecutorThreads = 0;
    private int serverSelectorThreads = 3;
    private int serverOnewaySemaphoreValue = 256;
    private int serverAsyncSemaphoreValue = 64;
    private int serverChannelMaxIdleTimeSeconds = 120;

    private int serverSocketSndBufSize = NettySystemConfig.socketSndbufSize;
    private int serverSocketRcvBufSize = NettySystemConfig.socketRcvbufSize;
    private boolean serverPooledByteBufAllocatorEnable = true;

    /**
     * make make install
     *
     *
     * ../glibc-2.10.1/configure \ --prefix=/usr \ --with-headers=/usr/include \
     * --host=x86_64-linux-gnu \ --build=x86_64-pc-linux-gnu \ --without-gd
     */
    private boolean useEpollNativeSelector = false;
}

其中namesrv.properties配置文件如下:

##
# 名称:NamesrvConfig.rocketmqHome <String>
# 默认值:(通过 sh mqnamesrv 设置 ROCKETMQ_HOME 环境变量,在源程序中获取环境变量得
#        到的目录)
# 描述:RocketMQ 主目录 
# 建议:不主动配置
##
rocketmqHome = /usr/rocketmq

##
# 名称:NamesrvConfig.kvConfigPath <String>
# 默认值:$user.home/namesrv/kvConfig.json <在源程序中获取用户环境变量后生成>
# 描述:kv 配置文件路径,包含顺序消息主题的配置信息 
# 建议:启用顺序消息时配置
##
kvConfigPath = /root/namesrv/kvConfig.json

##
# 名称:NamesrvConfig.configStorePath <String>
# 默认值:$user.home/namesrv/namesrv.properties <在源程序中获取用户环境变量后生成>
# 描述:NameServer 配置文件路径
# 建议:启动时通过 -c 指定
##
configStorePath = /root/namesrv/namesrv.properties

##
# 名称:NamesrvConfig.clusterTest <boolean>
# 默认值:false <在源程序中初始化字段时指定>
# 描述:是否开启集群测试
# 建议:不主动配置
##
clusterTest = false

##
# 名称:NamesrvConfig.orderMessageEnable <boolean>
# 默认值:false <在源程序中初始化字段时指定>
# 描述:是否支持顺序消息
# 建议:启用顺序消息时配置
##
orderMessageEnable = false

##
# 名称:NettyServerConfig.listenPort <int>
# 默认值:9876 <在源程序中初始化后单独设置>
# 描述:服务端监听端口
# 建议:不主动配置
##
listenPort = 9876

##
# 名称:NettyServerConfig.serverWorkerThreads <int>
# 默认值:8 <在源程序中初始化字段时指定>
# 描述:Netty 业务线程池线程个数
# 建议:不主动配置
##
serverWorkerThreads = 8

##
# 名称:NettyServerConfig.serverCallbackExecutorThreads <int>
# 默认值:0 <在源程序中初始化字段时指定>
# 描述:Netty public 任务线程池线程个数,Netty 网络设计,根据业务类型会创建不同的线程池,比如处理发送消息、消息消费、心跳检测等。如果该业务类型(RequestCode)未注册线程池,则由 public 线程池执行
# 建议:
##
serverCallbackExecutorThreads = 0

##
# 名称:NettyServerConfig.serverSelectorThreads <int>
# 默认值:3 <在源程序中初始化字段时指定>
# 描述:IO 线程池线程个数,主要是 NameServer、Broker 端解析请求、返回响应的线程个数,这类线程池主要是处理网络请求的,解析请求包,然后转发到各个业务线程池完成具体的业务操作,然后将结果再返回调用方
# 建议:不主动配置
##
serverSelectorThreads = 3

##
# 名称:NettyServerConfig.serverOnewaySemaphoreValue <int>
# 默认值:256 <在源程序中初始化字段时指定>
# 描述:send oneway 消息请求并发度
# 建议:不主动配置
##
serverOnewaySemaphoreValue = 256

##
# 名称:NettyServerConfig.serverAsyncSemaphoreValue <int>
# 默认值:64 <在源程序中初始化字段时指定>
# 描述:异步消息发送最大并发度
# 建议:不主动配置
##
serverAsyncSemaphoreValue = 64

##
# 名称:NettyServerConfig.serverChannelMaxIdleTimeSeconds <int>
# 默认值:120 <在源程序中初始化字段时指定>
# 描述:网络连接最大空闲时间,单位秒,如果连接空闲时间超过该参数设置的值,连接将被关闭
# 建议:不主动配置
##
serverChannelMaxIdleTimeSeconds = 120

##
# 名称:NettyServerConfig.serverSocketSndBufSize <int>
# 默认值:65535 <在源程序中初始化字段时指定>
# 描述:网络 socket 发送缓存区大小,单位 B,即默认为 64KB
# 建议:不主动配置
##
serverSocketSndBufSize = 65535

##
# 名称:NettyServerConfig.serverSocketRcvBufSize <int>
# 默认值:65535 <在源程序中初始化字段时指定>
# 描述:网络 socket 接收缓存区大小,单位 B,即默认为 64KB
# 建议:不主动配置
##
serverSocketRcvBufSize = 65535

##
# 名称:NettyServerConfig.serverPooledByteBufAllocatorEnable <int>
# 默认值:true <在源程序中初始化字段时指定>
# 描述:ByteBuffer 是否开启缓存,建议开启
# 建议:不主动配置
##
serverPooledByteBufAllocatorEnable = true

##
# 名称:NettyServerConfig.useEpollNativeSelector <int>
# 默认值:false <在源程序中初始化字段时指定>
# 描述:是否启用 Epoll IO 模型
# 建议:Linux 环境开启
##
useEpollNativeSelector = true

3.NamesrvContorller对象的生成

加载完配置文件之后,生成NamesrvController对象,并将namesrvConfignettyServerConfig配置保存在NamesrvController对象中;并且生成了kvConfigManager,routeInfoManager,brokerHousekeepingService以及configuration四个对象.

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);
    this.configuration = new Configuration(
        log,
        this.namesrvConfig, this.nettyServerConfig
    );
    this.configuration.setStorePathFromConfig(this.namesrvConfig, "configStorePath");
}

其中kvConfigManager主要用来保存键值对:

public class KVConfigManager {
    private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);

    private final NamesrvController namesrvController;

    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final HashMap<String/* Namespace */, HashMap<String/* Key */, String/* Value */>> configTable =
        new HashMap<String, HashMap<String, String>>();

    public KVConfigManager(NamesrvController namesrvController) {
        this.namesrvController = namesrvController;
    }
    ...
}

routeInfoManager保存路由信息:

public class RouteInfoManager {
    private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);
    private final static long BROKER_CHANNEL_EXPIRED_TIME = 1000 * 60 * 2;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    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);
    }
}

brokerHousekeepingService则是netty的监听器,处理异步事件的调用逻辑:

public class BrokerHousekeepingService implements ChannelEventListener {
    private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);
    private final NamesrvController namesrvController;

    public BrokerHousekeepingService(NamesrvController namesrvController) {
        this.namesrvController = namesrvController;
    }
	//连接
    @Override
    public void onChannelConnect(String remoteAddr, Channel channel) {
    }
	//关闭连接
    @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);
    }
}

configuration则是将配置持久化保存.

4.调用start()方法,启动namesrv

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

    if (null == controller) {
        throw new IllegalArgumentException("NamesrvController is null");
    }
	//调用controller.initialize()方法进行初始化
    boolean initResult = controller.initialize();
    //初始化失败,结束
    if (!initResult) {
        controller.shutdown();
        System.exit(-3);
    }
	//在进程关闭时,调用controller.shutdown()方法
    Runtime.getRuntime().addShutdownHook(new ShutdownHookThread(log, new Callable<Void>() {
        @Override
        public Void call() throws Exception {
            controller.shutdown();
            return null;
        }
    }));
	//调用controller.start()方法
    controller.start();

    return controller;
}

Runtime.getRuntime().addShutdownHook()方法的作用:
jvm中增加一个关闭的钩子,当jvm关闭的时候,会执行系统中已经设置的所有通过方法addShutdownHook添加的钩子,当系统执行完这些钩子后,jvm才会关闭。所以这些钩子可以在jvm关闭的时候进行内存清理、对象销毁等操作。

  • controller.initialize()

    首先是controller的初始化方法:

    public boolean initialize() {
    	//加载namesrvController对象中kvConfigPath路径中存储的kvConfig.json文件中的键值对,
        //将这些属性存储在KVConfigManager.configTable对象中
        this.kvConfigManager.load();
    	//根据nettyServerConfig(配置),brokerHousekeepingService(监听器)启动netty服务器
        this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);
    	// 初始化负责处理Netty网络交互数据的线程池
        this.remotingExecutor =
            Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), new ThreadFactoryImpl("RemotingExecutorThread_"));
    
        this.registerProcessor();
    	// 注册心跳机制线程池
        this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    
            @Override
            public void run() {
                NamesrvController.this.routeInfoManager.scanNotActiveBroker();
            }
        }, 5, 10, TimeUnit.SECONDS);
    	// 注册打印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;
    }
    

    kvConfigManager.load()方法将配置加载到kvConfigManager.configTable对象中:

    public void load() {
        String content = null;
        try {
            content = MixAll.file2String(this.namesrvController.getNamesrvConfig().getKvConfigPath());
        } catch (IOException e) {
            log.warn("Load KV config table exception", e);
        }
        if (content != null) {
            KVConfigSerializeWrapper kvConfigSerializeWrapper =
                KVConfigSerializeWrapper.fromJson(content, KVConfigSerializeWrapper.class);
            if (null != kvConfigSerializeWrapper) {
                this.configTable.putAll(kvConfigSerializeWrapper.getConfigTable());
                log.info("load KV config table OK");
            }
        }
    }
    

    Netty服务器的启动配置初始化:

    public NettyRemotingServer(final NettyServerConfig nettyServerConfig,
                               final ChannelEventListener channelEventListener) {
        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());
            }
        });
    	//生成EventLoopGroup主从线程池
        if (useEpoll()) {
            this.eventLoopGroupBoss = new EpollEventLoopGroup(1, new ThreadFactory() {
                private AtomicInteger threadIndex = new AtomicInteger(0);
    
                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, String.format("NettyEPOLLBoss_%d", this.threadIndex.incrementAndGet()));
                }
            });
    
            this.eventLoopGroupSelector = new EpollEventLoopGroup(nettyServerConfig.getServerSelectorThreads(), new ThreadFactory() {
                private AtomicInteger threadIndex = new AtomicInteger(0);
                private int threadTotal = nettyServerConfig.getServerSelectorThreads();
    
                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, String.format("NettyServerEPOLLSelector_%d_%d", threadTotal, this.threadIndex.incrementAndGet()));
                }
            });
        } else {
            this.eventLoopGroupBoss = new NioEventLoopGroup(1, new ThreadFactory() {
                private AtomicInteger threadIndex = new AtomicInteger(0);
    
                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, String.format("NettyNIOBoss_%d", this.threadIndex.incrementAndGet()));
                }
            });
    
            this.eventLoopGroupSelector = new NioEventLoopGroup(nettyServerConfig.getServerSelectorThreads(), new ThreadFactory() {
                private AtomicInteger threadIndex = new AtomicInteger(0);
                private int threadTotal = nettyServerConfig.getServerSelectorThreads();
    
                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, String.format("NettyServerNIOSelector_%d_%d", threadTotal, this.threadIndex.incrementAndGet()));
                }
            });
        }
    
        loadSslContext();
    }
    

    主要是netty服务端的启动过程,配置文件以及监听器由上层传入.并且根据配置文件中的useEpollNativeSelector = true属性的值生成不同的主从线程池.

    loadSslContext()方法完成SSL/TLS协议的设置.

    调用registerProcessor()方法,注册处理网络请求的线程池:

    private void registerProcessor() {
        //默认不启动集群测试
        if (namesrvConfig.isClusterTest()) {
    
            this.remotingServer.registerDefaultProcessor(new ClusterTestRequestProcessor(this, namesrvConfig.getProductEnvName()),this.remotingExecutor);
        } else {
    		//DefaultRequestProcessor:默认请求处理器 remotingExecutor:线程池
            this.remotingServer.registerDefaultProcessor(new DefaultRequestProcessor(this), this.remotingExecutor);
        }
    }
    

    DefaultRequestProcessor实例注册给Netty服务器,至此Netty服务端的配置完成.

    接下来启动两个定时任务:

    1. 进行心跳检测,定时清除不活跃的broker,scanNotActiveBroker()方法:

      public void scanNotActiveBroker() {
          Iterator<Entry<String, BrokerLiveInfo>> it = this.brokerLiveTable.entrySet().iterator();
          while (it.hasNext()) {
              Entry<String, BrokerLiveInfo> next = it.next();
              long last = next.getValue().getLastUpdateTimestamp();
              if ((last + BROKER_CHANNEL_EXPIRED_TIME) < System.currentTimeMillis()) {
                  RemotingUtil.closeChannel(next.getValue().getChannel());
                  it.remove();
                  log.warn("The broker channel expired, {} {}ms", next.getKey(), BROKER_CHANNEL_EXPIRED_TIME);
                  this.onChannelDestroy(next.getKey(), next.getValue().getChannel());
              }
          }
      }
      

      可以看到Namesrv会遍历brokerLiveTable这个map的key,并且获取器上次活跃时间getLastUpdateTimestamp(),如果满足(last + BROKER_CHANNEL_EXPIRED_TIME) < System.currentTimeMillis()则端开连接.
      private final static long BROKER_CHANNEL_EXPIRED_TIME = 1000 * 60 * 2;

    2. 定期打印日志,调用printAllPeriodically()方法:

      public void printAllPeriodically() {
          try {
              this.lock.readLock().lockInterruptibly();
              try {
                  log.info("--------------------------------------------------------");
      
                  {
                      log.info("configTable SIZE: {}", this.configTable.size());
                      Iterator<Entry<String, HashMap<String, String>>> it =
                          this.configTable.entrySet().iterator();
                      while (it.hasNext()) {
                          Entry<String, HashMap<String, String>> next = it.next();
                          Iterator<Entry<String, String>> itSub = next.getValue().entrySet().iterator();
                          while (itSub.hasNext()) {
                              Entry<String, String> nextSub = itSub.next();
                              log.info("configTable NS: {} Key: {} Value: {}", next.getKey(), nextSub.getKey(),
                                       nextSub.getValue());
                          }
                      }
                  }
              } finally {
                  this.lock.readLock().unlock();
              }
          } catch (InterruptedException e) {
              log.error("printAllPeriodically InterruptedException", e);
          }
      }
      

      遍历configTable完成日志记录.

      完成之后注册一个监听器重新加载SslContext,至此初始化结束.

  • controller.start()

    初始化完成之后调用start()方法启动netty服务端.

    public void start() throws Exception {
        //启动netty服务端
        this.remotingServer.start();
    
        if (this.fileWatchService != null) {
            //启动重新加载SslContext线程
            this.fileWatchService.start();
        }
    }
    
    

    接下来进行netty服务端的启动流程,start()方法:

    @Override
    public void start() {
        //开启默认业务处理线程池
        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());
                }
            });
    
        prepareSharableHandlers();
    
        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()))
            .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);
        }
    
        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);
    }
    
    

    可以看到在childHandler构造的过程中向pipeline中注入了 NettyServerHandler对象:

    @ChannelHandler.Sharable
        class NettyServerHandler extends SimpleChannelInboundHandler<RemotingCommand> {
    
            @Override
            protected void channelRead0(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception {
                processMessageReceived(ctx, msg);
            }
        }
    
    

    processMessageReceived方法用来处理消息的传输:

    public void processMessageReceived(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception {
        final RemotingCommand cmd = msg;
        if (cmd != null) {
            switch (cmd.getType()) {
                case REQUEST_COMMAND:
                    processRequestCommand(ctx, cmd);
                    break;
                case RESPONSE_COMMAND:
                    processResponseCommand(ctx, cmd);
                    break;
                default:
                    break;
            }
        }
    }
    
    

    根据请求的类型(REQUEST_COMMANDRESPONSE_COMMAND)调用不同的处理方法:

    REQUEST_COMMAND

    public void processRequestCommand(final ChannelHandlerContext ctx, final RemotingCommand cmd) {
        final Pair<NettyRequestProcessor, ExecutorService> matched = this.processorTable.get(cmd.getCode());
        final Pair<NettyRequestProcessor, ExecutorService> pair = null == matched ? this.defaultRequestProcessor : matched;
        final int opaque = cmd.getOpaque();
    
        if (pair != null) {
            Runnable run = new Runnable() {
                @Override
                public void run() {
                    try {
                        doBeforeRpcHooks(RemotingHelper.parseChannelRemoteAddr(ctx.channel()), cmd);
                        final RemotingResponseCallback callback = new RemotingResponseCallback() {
                            @Override
                            public void callback(RemotingCommand response) {
                                doAfterRpcHooks(RemotingHelper.parseChannelRemoteAddr(ctx.channel()), cmd, response);
                                if (!cmd.isOnewayRPC()) {
                                    if (response != null) {
                                        response.setOpaque(opaque);
                                        response.markResponseType();
                                        try {
                                            ctx.writeAndFlush(response);
                                        } catch (Throwable e) {
                                            log.error("process request over, but response failed", e);
                                            log.error(cmd.toString());
                                            log.error(response.toString());
                                        }
                                    } else {
                                    }
                                }
                            }
                        };
                        if (pair.getObject1() instanceof AsyncNettyRequestProcessor) {
                            AsyncNettyRequestProcessor processor = (AsyncNettyRequestProcessor)pair.getObject1();
                            processor.asyncProcessRequest(ctx, cmd, callback);
                        } else {
                            NettyRequestProcessor processor = pair.getObject1();
                            RemotingCommand response = processor.processRequest(ctx, cmd);
                            callback.callback(response);
                        }
                    } catch (Throwable e) {
                        log.error("process request exception", e);
                        log.error(cmd.toString());
    
                        if (!cmd.isOnewayRPC()) {
                            final RemotingCommand response = RemotingCommand.createResponseCommand(RemotingSysResponseCode.SYSTEM_ERROR,
                                                                                                   RemotingHelper.exceptionSimpleDesc(e));
                            response.setOpaque(opaque);
                            ctx.writeAndFlush(response);
                        }
                    }
                }
            };
    
            if (pair.getObject1().rejectRequest()) {
                final RemotingCommand response = RemotingCommand.createResponseCommand(RemotingSysResponseCode.SYSTEM_BUSY,
                                                                                       "[REJECTREQUEST]system busy, start flow control for a while");
                response.setOpaque(opaque);
                ctx.writeAndFlush(response);
                return;
            }
    
            try {
                final RequestTask requestTask = new RequestTask(run, ctx.channel(), cmd);
                pair.getObject2().submit(requestTask);
            } catch (RejectedExecutionException e) {
                if ((System.currentTimeMillis() % 10000) == 0) {
                    log.warn(RemotingHelper.parseChannelRemoteAddr(ctx.channel())
                             + ", too many requests and system thread pool busy, RejectedExecutionException "
                             + pair.getObject2().toString()
                             + " request code: " + cmd.getCode());
                }
    
                if (!cmd.isOnewayRPC()) {
                    final RemotingCommand response = RemotingCommand.createResponseCommand(RemotingSysResponseCode.SYSTEM_BUSY,
                                                                                           "[OVERLOAD]system busy, start flow control for a while");
                    response.setOpaque(opaque);
                    ctx.writeAndFlush(response);
                }
            }
        } else {
            String error = " request type " + cmd.getCode() + " not supported";
            final RemotingCommand response =
                RemotingCommand.createResponseCommand(RemotingSysResponseCode.REQUEST_CODE_NOT_SUPPORTED, error);
            response.setOpaque(opaque);
            ctx.writeAndFlush(response);
            log.error(RemotingHelper.parseChannelRemoteAddr(ctx.channel()) + error);
        }
    }
    
    

    这段代码主要是提交了一个Runnable给线程池,其中核心方法事对请求的处理:

    NettyRequestProcessor processor = pair.getObject1();
    RemotingCommand response = processor.processRequest(ctx, cmd);
    
    

    processor就是初始化时注入的DefaultRequestProcessor对象,调用其processRequest方法:

    public RemotingCommand processRequest(ChannelHandlerContext ctx,
                                          RemotingCommand request) throws RemotingCommandException {
    
        if (ctx != null) {
            log.debug("receive request, {} {} {}",
                      request.getCode(),
                      RemotingHelper.parseChannelRemoteAddr(ctx.channel()),
                      request);
        }
    
    
        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_ROUTEINFO_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;
        }
        return null;
    }
    
    

    通过请求中不同的code调用不同的方法(注册broker,获取topic的路由信息等),通过更新维护之前存储信息的map实现.

  • controller.shutdown()

    优雅的关闭. NameServer启动的最后一步,是注册了一个JVM的钩子函数,它会在JVM关闭之前执行。这个钩子函数的作用是释放资源,如关闭Netty服务器,关闭线程池等。

    public void shutdown() {
        //关闭netty服务端
        this.remotingServer.shutdown();
        //关闭线程池
        this.remotingExecutor.shutdown();
        //关闭定时任务
        this.scheduledExecutorService.shutdown();
    	//关闭ssl线程
        if (this.fileWatchService != null) {
            this.fileWatchService.shutdown();
        }
    }
    
    

    Namesrv路由

Namesrv路由元数据

Namesrv启动时生成routeInfoManager用来保存路由信息:

public class RouteInfoManager {
    private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);
    private final static long BROKER_CHANNEL_EXPIRED_TIME = 1000 * 60 * 2;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    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);
    }
    ...
}

可以看到在构造方法中生成了五个map:

topicQueueTable:Topic消息队列路由信息,消息发送时根据路由表进行负载均衡;

brokerAddrTable:Broker基础信息,包括BrokerName,所属集群名,主备Broker地址;

clusterAddrTable:Broker集群信息,集群中所有Broker名称;

brokerLiveTable:Broker状态信息,Namesrv每次收到心跳包都会替换该信息;

filterServerTable:Broker上的FilterServer列表,用于类模式消息过滤.

路由-心跳检测(路由注册/删除机制)

  • 机制一-监测连接

    Namesrv启动时实现了netty时间监听的接口:

    public class BrokerHousekeepingService implements ChannelEventListener {
        private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);
        private final NamesrvController namesrvController;
    
        public BrokerHousekeepingService(NamesrvController namesrvController) {
            this.namesrvController = namesrvController;
        }
    	//连接
        @Override
        public void onChannelConnect(String remoteAddr, Channel channel) {
        }
    	//关闭连接
        @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);
        }
    }
    

    该接口用于netty服务端监听到对应的事件后回调.即当Broker连接,关闭连接,异常以及空闲时维护路由信息表.

  • 机制二-心跳检测

    BrokerNamesrv定时发送心跳包,在Broker启动时有如下代码:

    this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    try {
                        BrokerController.this.registerBrokerAll(true, false, brokerConfig.isForceRegister());
                    } catch (Throwable e) {
                        log.error("registerBrokerAll Exception", e);
                    }
                }
            }, 1000 * 10, Math.max(10000, Math.min(brokerConfig.getRegisterNameServerPeriod(), 60000)), TimeUnit.MILLISECONDS);
    

    创建一个定时线程提交到线程池,定时向Namesrv提交注册信息,其中RegisterNameServerPeriod的值可以在broker.conf文件中配置,默认为registerNameServerPeriod = 1000 * 3030s;

    结合Namesrv启动时为netty服务端注入的请求处理器DefaultRequestProcessor对象的processRequest方法:

    public RemotingCommand processRequest(ChannelHandlerContext ctx,
                                          RemotingCommand request) throws RemotingCommandException {
    
        if (ctx != null) {
            log.debug("receive request, {} {} {}",
                      request.getCode(),
                      RemotingHelper.parseChannelRemoteAddr(ctx.channel()),
                      request);
        }
    
    
        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_ROUTEINFO_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;
        }
        return null;
    }
    

    通过上述方法完成对路由信息表的维护.

Namesrv接到心跳包之后维护路由表,并且Namesrv启动时也提交了定时线程:

this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {

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

//----------------------------------------------------------------------
public void scanNotActiveBroker() {
        Iterator<Entry<String, BrokerLiveInfo>> it = this.brokerLiveTable.entrySet().iterator();
        while (it.hasNext()) {
            Entry<String, BrokerLiveInfo> next = it.next();
            long last = next.getValue().getLastUpdateTimestamp();
            if ((last + BROKER_CHANNEL_EXPIRED_TIME) < System.currentTimeMillis()) {
                RemotingUtil.closeChannel(next.getValue().getChannel());
                it.remove();
                log.warn("The broker channel expired, {} {}ms", next.getKey(), BROKER_CHANNEL_EXPIRED_TIME);
                this.onChannelDestroy(next.getKey(), next.getValue().getChannel());
            }
        }
    }

该线程用于定时扫描brokerLiveTable路由表,并移除超时未上报信息的Broker.

路由发现

Broker宕机后,Namesrv并不会主动向Producer/Consumer推送消息,而是Producer/Consumer周期性的从Namesrv拉取路由信息.

同样是上述处理请求的方法,对应事件RequestCode.GET_ROUTEINFO_BY_TOPIC,通过getRouteInfoByTopic方法获取路由信息:

public RemotingCommand getRouteInfoByTopic(ChannelHandlerContext ctx,
        RemotingCommand request) throws RemotingCommandException {
        final RemotingCommand response = RemotingCommand.createResponseCommand(null);
        final GetRouteInfoRequestHeader requestHeader =
            (GetRouteInfoRequestHeader) request.decodeCommandCustomHeader(GetRouteInfoRequestHeader.class);

        TopicRouteData topicRouteData = this.namesrvController.getRouteInfoManager().pickupTopicRouteData(requestHeader.getTopic());

        if (topicRouteData != null) {
            if (this.namesrvController.getNamesrvConfig().isOrderMessageEnable()) {
                String orderTopicConf =
                    this.namesrvController.getKvConfigManager().getKVConfig(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG,
                        requestHeader.getTopic());
                topicRouteData.setOrderTopicConf(orderTopicConf);
            }

            byte[] content = topicRouteData.encode();
            response.setBody(content);
            response.setCode(ResponseCode.SUCCESS);
            response.setRemark(null);
            return response;
        }

        response.setCode(ResponseCode.TOPIC_NOT_EXIST);
        response.setRemark("No topic route info in name server for the topic: " + requestHeader.getTopic()
            + FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL));
        return response;
    }


//----------------------------------------------------------------------------------
public TopicRouteData pickupTopicRouteData(final String topic) {
        TopicRouteData topicRouteData = new TopicRouteData();
        boolean foundQueueData = false;
        boolean foundBrokerData = false;
        Set<String> brokerNameSet = new HashSet<String>();
        List<BrokerData> brokerDataList = new LinkedList<BrokerData>();
        topicRouteData.setBrokerDatas(brokerDataList);

        HashMap<String, List<String>> filterServerMap = new HashMap<String, List<String>>();
        topicRouteData.setFilterServerTable(filterServerMap);

        try {
            try {
                this.lock.readLock().lockInterruptibly();
                List<QueueData> queueDataList = this.topicQueueTable.get(topic);
                if (queueDataList != null) {
                    topicRouteData.setQueueDatas(queueDataList);
                    foundQueueData = true;

                    Iterator<QueueData> it = queueDataList.iterator();
                    while (it.hasNext()) {
                        QueueData qd = it.next();
                        brokerNameSet.add(qd.getBrokerName());
                    }

                    for (String brokerName : brokerNameSet) {
                        BrokerData brokerData = this.brokerAddrTable.get(brokerName);
                        if (null != brokerData) {
                            BrokerData brokerDataClone = new BrokerData(brokerData.getCluster(), brokerData.getBrokerName(), (HashMap<Long, String>) brokerData
                                .getBrokerAddrs().clone());
                            brokerDataList.add(brokerDataClone);
                            foundBrokerData = true;
                            for (final String brokerAddr : brokerDataClone.getBrokerAddrs().values()) {
                                List<String> filterServerList = this.filterServerTable.get(brokerAddr);
                                filterServerMap.put(brokerAddr, filterServerList);
                            }
                        }
                    }
                }
            } finally {
                this.lock.readLock().unlock();
            }
        } catch (Exception e) {
            log.error("pickupTopicRouteData Exception", e);
        }

        log.debug("pickupTopicRouteData {} {}", topic, topicRouteData);

        if (foundBrokerData && foundQueueData) {
            return topicRouteData;
        }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值