文章目录
NameServer作用
在介绍NameServer前,先看下官方的RocketMQ架构图
NameServer是一个非常简单的Topic路由注册中心,其角色类似Dubbo中的zookeeper,支持Broker的动态注册与发现。主要包括两个功能
- Broker管理,NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制,检查Broker是否还存活;
- 路由信息管理,每个NameServer将保存关于Broker集群的整个路由信息和用于客户端查询的队列信息。然后Producer和Conumser通过NameServer就可以知道整个Broker集群的路由信息,从而进行消息的投递和消费
NameServer通常也是集群的方式部署,各实例间相互不进行信息通讯。Broker是向每一台NameServer注册自己的路由信息,所以每一个NameServer实例上面都保存一份完整的路由信息。当某个NameServer因某种原因下线了,Broker仍然可以向其它NameServer同步其路由信息,Producer,Consumer仍然可以动态感知Broker的路由的信息
NameServer实例时间互不通信,这本身也是其设计亮点之一,即允许不同NameServer之间数据不同步(像Zookeeper那样保证各节点数据强一致性会带来额外的性能消耗)
本文会从源码角度重点分析NameServer的启动流程
下篇文章会主要分析NameServer对broker数据的管理,以及路由机制
NameServer启动
先给出NameServer启动的时序图,方便大家跟踪流程
NameServer配置加载
时序图第一步加载配置文件对应源代码在org.apache.rocketmq.namesrv.NamesrvStartup#main0方法中
从NamesrvConfig,NettyServerConfig中加载配置文件代码在createNamesrvController方法中,看下具体实现
与配置相关的两个类为NamesrvConfig和NettyServerConfig
NamesrvConfig中属性如下
//rocketmqHome路径
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";
private boolean clusterTest = false;
//是否允许顺序消息,默认不开启
private boolean orderMessageEnable = false;
NettyServerConfig中配置如下,mq底层基于Netty实现通讯,NettyServerConfig中参数使用会在NettyRemotingServer初始化及启动时做详细介绍,大家先有个大概了解
//NettyServer监听端口
private int listenPort = 8888;
//Netty默认事件处理线程池,处理如broker注册,topic路由信息查询、topic删除等与producer、broker交互request
private int serverWorkerThreads = 8;
//与serverWorkerThreads类似,事件处理器注册时如果没指定线程池时,使用serverCallbackExecutorThreads指定的公用publicExecutor来处理特定业务交互命令
private int serverCallbackExecutorThreads = 0;
//Netty Selector线程数量
private int serverSelectorThreads = 3;
//单向发送信号量,防止单向发送请求并发度过高
private int serverOnewaySemaphoreValue = 256;
//异步发送信号量,防止单向发送请求并发度过高
private int serverAsyncSemaphoreValue = 64;
//通道空闲时间 默认120秒
private int serverChannelMaxIdleTimeSeconds = 120;
//socket发送缓冲区大小
private int serverSocketSndBufSize = NettySystemConfig.socketSndbufSize;
//接收缓冲区大小
private int serverSocketRcvBufSize = NettySystemConfig.socketRcvbufSize;
private boolean serverPooledByteBufAllocatorEnable = true;
//是否启用Epoll IO模型,Linux环境建议开启
private boolean useEpollNativeSelector = false;
在nameServer启动时也可以通过-c命令指定配置文件地址来加载外部配置,至此NameServer配置加载完成,返回一个NamesrvController对象,进入初始化、启动步骤
NameServerController初始化
初始化、启动代码如下
先看下初始化方法initialize方法,对应时序图中3,4,5,6,7步骤,下面逐个分析
kvConfig加载
@3:读取kvConfigPath下的文件,并转化成类型为HashMap<String/* Namespace */, HashMap<String/* Key */, String/* Value */>>
的Map中,nameServer中相关配置信息存储,均采用线程不安全的HashMap容器存储,结合读写锁ReadWriteLock最大化并发度,KvConfig就是一个典型的例子,后续路由信息的存储也是同理,读写锁如图
初始化NettyRemotingServer
@4:初始化NettyRemotingServer,对应代码如下,NettyServerConfig为上述提到Netty的配置类,BrokerHousekeepingService实现了ChannelEventListener,用于Channel通道关闭时调用RouteInfoManager#onChannelDestroy清除路由信息
this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);
下面进到NettyRemotingServer构造方法看下具体内容
其中publicExecutor会在,注册特定业务Netty事件处理器时用到,特定业务指的是发送消息,查询消息等业务
具体业务注册事件处理线程池的地方在BrokerController中:
注册默认事件处理线程池
@5:注册默认事件处理器
那么什么是默认事件呢?就是Producer,Broker与NameServer的交互事件,如Broker注册,Topic删除等事件,处理默认事件的代码逻辑在DefaultRequestProcessor中,如图所示,大家从名字上就能看出有哪些交互
定时器scanNotActiveBroker
@6:文章开头提到NameServer通常也是集群的方式部署,各实例间相互不进行信息通讯,那么Broker实例挂掉之后,NameServer如何感知呢?Broker启动时,会开启定时器,定时向每个NameServer进行心跳注册,注册时,会将Broker存活信息存储在RouteInfoManager#brokerLiveTable中
private final HashMap<String/* brokerAddr */, BrokerLiveInfo> brokerLiveTable;
BrokerLiveInfo属性信息如下,关键在于注册时将当前时间存储在了lastUpdateTimestamp字段中
//上次心跳注册时间
private long lastUpdateTimestamp;
private DataVersion dataVersion;
private Channel channel;
private String haServerAddr;
回到scanNotActiveBroker中,其定时扫描brokerLiveTable,将过期的broker信息移除
默认broker失效时间为120秒,超过120秒没收到来自broker的心跳,则清除该 broker路由信息,具体路由相关分析在下篇文章介绍
定时器printAllPeriodically
@7:定时输出kvConfig信息,具体为什么要定时输出,我估计着大概是为了定时查看实时配置信息吧
JVM钩子函数注册
@8:NameServer初始化完后,先向JVM注册一个钩子函数,在JVM关闭时对NameServerController中线程池,NettyServer进行关闭,大家可以借鉴这种写法来关闭自己业务中用到的线程池
线程池的shutDown方法调用后不再接收新提交的任务,同时等待线程池里的任务执行完毕后关闭线程池
NameServerController启动
@9:NameServerController启动代码的核心在NettyRemotingServer的start方法中,主要是Netty相关配置
本文主要介绍了NameServer相关配置及启动流程,NameServer作为路由中心,更重要的自然是NameServer对broker数据的管理,以及路由机制,下篇文章重点介绍这两点