关于 RocketMQ 源码的下载和构建,这里不多重复,很简单,相信大家都搞的定。
在前面的文章中,我们分析了 RocketMQ 的整体结构,其中也说明了 NameServer 的 RocketMQ 体系中的作用。在启动 NameServer 时,我们会使用类似下面的命令:
nohup sh bin/mqnamesrv &
在 mqnamesrv 脚本中的最后一行,我们看到这样的语句:
sh ${ROCKETMQ_HOME}/bin/runserver.sh org.apache.rocketmq.namesrv.NamesrvStartup $@
这里我们就看到 NameServer 启动时的主类了,也就是 NamesrvStartup,我们来看它的 main 方法实现:
public static void main(String[] args) {
main0(args);
}
public static NamesrvController main0(String[] args) {
try {
/* 创建 NameServer 控制器,NameServerController 是 NameServer 的核心控制器 */
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;
}
下面会涉及到 org.apache.commons.cli 的使用,我们来简单说明一下:Apache Commons CLI 库提供了一个 API,用于解析传递给程序的命令行选项。 它还能够打印命令行工具可用选项的详细说明和帮助消息。
使用时我需要需要引入 maven 依赖:
<dependency>
<groupId>commons-cligroupId>
<artifactId>commons-cliartifactId>
<version>1.4version>
dependency>
NamesrvStartup:
public static NamesrvController createNamesrvController(String[] args) throws IOException, JoranException {
System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, Integer.toString(MQVersion.CURRENT_VERSION));
// 构建命令行选项,添加 -h -n 选项
Options options = ServerUtil.buildCommandlineOptions(new Options());
// 添加 -c -p 选项,解析命令行
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 选项可以指定 NameServer 配置文件的地址
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);
// 将配置文件中的配置通过反射 setter 方法设置到 NamesrvConfig 和 NettyServerConfig 的对应属性上
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);
}
// 将命令行选项的完整名称(eg:-n --> namesrvAddr)对应的选项值设置到 NettyServerConfig 的对应属性上
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);
// 日志打印 NamesrvConfig 和 NettyServerConfig 和配置
MixAll.printObjectProperties(log, namesrvConfig);
MixAll.printObjectProperties(log, nettyServerConfig);
final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig);
// 记住所有的配置,以防止被丢弃掉
controller.getConfiguration().registerConfig(properties);
return controller;
}
这里提到了两个配置类 NamesrvConfig 和 NettyServerConfig,我们分别来看下两个配置。NamesrvConfig 的作用是配置 NameServer 的业务参数,NamesrvConfig 主要的配置项如下:
NamesrvConfig:
// rocketmq 主目录,可以通过 -Drocketmq.home.dir=path 或通过设置环境变量 ROCKETMQ_HOME 来配置 RocketMQ 的主目录
private String rocketmqHome = System.getProperty(MixAll.ROCKETMQ_HOME_PROPERTY, System.getenv(MixAll.ROCKETMQ_HOME_ENV));
// NameServer 存储 KV 配置属性的持久化路径
private String kvConfigPath = System.getProperty("user.home") + File.separator + "namesrv" + File.separator + "kvConfig.json";
// NameServer 默认配置文件路径,不生效。NameServer 启动时如果要通过配置文件配置NameServer 启动属性的话,可以使用 -c 选项
private String configStorePath = System.getProperty("user.home") + File.separator + "namesrv" + File.separator + "namesrv.properties";
// 是否支持顺序消息,默认是不支持
private boolean orderMessageEnable = false;
NettyServerConfig 的作用是配置 NameServer 的网络参数,NettyServerConfig 主要的配置项如下:
NettyServerConfig:
// NameServer 监听端口,该值默认会被初始化为 9876
private int listenPort = 8888;
// Netty 业务线程池线程个数
private int serverWorkerThreads = 8;
// Netty public 任务线程池线程个数,RocketMQ 会根据业务类型会创建不同的线程池,比如处理消息发送、消息消费、心跳检测等。如果该业务类型(RequestCode)未注册线程池,则由 public 线程池执行
private int serverCallbackExecutorThreads = 0;
// IO 线程池线程个数,主要是 NameServer、Broker 端解析请求、返回响应的线程个数,这类线程主要是处理网络请求的,解析请求包,然后转发到各个业务线程池完成具体的业务操作,然后将结果再返回调用方
private int serverSelectorThreads = 3;
// send oneway 消息请求并发度(Broker 端参数)
private int serverOnewaySemaphoreValue = 256;
// 异步消息发送最大并发度(Broker 端参数)
private int serverAsyncSemaphoreValue = 64;
// 网络连接最大空闲时间,默认120s。如果连接空闲时间超过该参数设置的值,连接将被关闭
private int serverChannelMaxIdleTimeSeconds = 120;
// 网络 socket 发送缓冲区大小,默认64k
private int serverSocketSndBufSize = NettySystemConfig.socketSndbufSize;
// 网络 socket 接收缓冲区大小,默认64k
private int serverSocketRcvBufSize = NettySystemConfig.socketRcvbufSize;
// 是否启用 PooledByteBufAllocator,默认启用
private boolean serverPooledByteBufAllocatorEnable = true;
// 是否启用 Epoll IO 模型,Linux 环境建议开启
private boolean useEpollNativeSelector = false;
继续分析 NameServer 的启动流程:
NamesrvStartup:
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);
}
// 添加关闭钩子,在停止系统时关闭控制器
Runtime.getRuntime().addShutdownHook(new ShutdownHookThread(log, new Callable<Void>() {
@Override
public Void call() throws Exception {
controller.shutdown();
return null;
}
}));
/* 启动控制器 */
controller.start();
return controller;
}
NamesrvController:
public boolean initialize() {
// 加载KV配置表
this.kvConfigManager.load();
/* 构造 NettyRemotingServer */
this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);
// remoting 线程池
this.remotingExecutor =
Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), new ThreadFactoryImpl("RemotingExecutorThread_"));
// 注册默认请求处理器,用于每个请求代码在处理器表中没有完全匹配的情况,处理请求时使用的线程池即为 remotingExecutor
this.registerProcessor();
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
// 每隔10秒扫描失联的 broker 移除掉,Broker 路由信息管理这部分内容比较独立,我们用单独的文章分析
NamesrvController.this.routeInfoManager.scanNotActiveBroker();
}
}, 5, 10, TimeUnit.SECONDS);
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
// 每隔10分钟打印 KV 配置表的内容
NamesrvController.this.kvConfigManager.printAllPeriodically();
}
}, 1, 10, TimeUnit.MINUTES);
if (TlsSystemConfig.tlsMode != TlsMode.DISABLED) {
// 注册一个监听器以重新加载 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;
}
这里的 routeInfoManager 属性是在 NamesrvController 构造方法中初始化的,它的作用是管理路由元数据,这部分内容我们会用单独的文章来分析。
NettyRemotingServer:
public NettyRemotingServer(final NettyServerConfig nettyServerConfig,
final ChannelEventListener channelEventListener) {
// 指定 oneway 和异步信号量的容量
super(nettyServerConfig.getServerOnewaySemaphoreValue(), nettyServerConfig.getServerAsyncSemaphoreValue());
this.serverBootstrap = new ServerBootstrap();
this.nettyServerConfig = nettyServerConfig;
this.channelEventListener = channelEventListener;
int publicThreadNums = nettyServerConfig.getServerCallbackExecutorThreads();
// 公共线程池数量,默认4
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());
}
});
// 判断是否使用 epoll 构建 selector 线程池(其实就是我们常说的 worker 线程)
// 判断条件:linux 平台 && 用户指定了使用 epoll && epoll 可用
if (useEpoll()) {
// boss 线程池
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 {
// boss 线程池
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()));
}
});
}
// 加载 ssl 上下文
loadSslContext();
}
这里涉及到 netty 的 API 调用,不熟悉 netty 可以查阅相关资料,也可以查看笔者关于 netty 的源码分析文章。
接下来就是控制器的启动:
NamesrvController:
public void start() throws Exception {
/* 启动 netty server */
this.remotingServer.start();
if (this.fileWatchService != null) {
// 启动重新加载 SslContext 监听器线程
this.fileWatchService.start();
}
}
NettyRemotingServer:
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());
}
});
// netty api 的调用,构建 ServerBootstrap
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,
new HandshakeHandler(TlsSystemConfig.tlsMode))
.addLast(defaultEventExecutorGroup,
// 编码器
new NettyEncoder(),
// 解码器
new NettyDecoder(),
// netty提供的handler,用于当Channel没有执行读、写或两者操作时,触发IdleStateEvent
new IdleStateHandler(0, 0, nettyServerConfig.getServerChannelMaxIdleTimeSeconds()),
// 连接管理
new NettyConnectManageHandler(),
// 请求和响应处理器
new NettyServerHandler()
);
}
});
if (nettyServerConfig.isServerPooledByteBufAllocatorEnable()) {
childHandler.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
}
try {
// 将 channel 的 socket 绑定到本地地址,并配置 socket 以侦听连接,同步等待
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);
}
NettyRemotingAbstract:
public void scanResponseTable() {
// 记录所有移除的请求
final List<ResponseFuture> rfList = new LinkedList<ResponseFuture>();
// responseTable 缓存所有正在进行的请求
Iterator<Entry<Integer, ResponseFuture>> it = this.responseTable.entrySet().iterator();
while (it.hasNext()) {
Entry<Integer, ResponseFuture> next = it.next();
ResponseFuture rep = next.getValue();
// 判断请求是否过期
if ((rep.getBeginTimestamp() + rep.getTimeoutMillis() + 1000) <= System.currentTimeMillis()) {
// 释放信号量
rep.release();
// 移除当前请求
it.remove();
// 添加到移除请求列表中
rfList.add(rep);
log.warn("remove timeout request, " + rep);
}
}
for (ResponseFuture rf : rfList) {
try {
// 在回调执行器中执行回调。如果回调执行器为 null,则直接在当前线程中运行
executeInvokeCallback(rf);
} catch (Throwable e) {
log.warn("scanResponseTable, operationComplete Exception", e);
}
}
}
最后我们来看停止动作:
public void shutdown() {
/* 停止 NettyRemotingServer */
this.remotingServer.shutdown();
// 停止 remoting 线程池
this.remotingExecutor.shutdown();
// 停止定时任务线程池
this.scheduledExecutorService.shutdown();
if (this.fileWatchService != null) {
// 停止 SslContext 监听器
this.fileWatchService.shutdown();
}
}
public void shutdown() {
try {
if (this.timer != null) {
// 取消 timer
this.timer.cancel();
}
// 停止 boss 线程池
this.eventLoopGroupBoss.shutdownGracefully();
// 停止 worker 线程池
this.eventLoopGroupSelector.shutdownGracefully();
if (this.nettyEventExecutor != null) {
// 停止 netty 事件执行器
this.nettyEventExecutor.shutdown();
}
if (this.defaultEventExecutorGroup != null) {
// 停止默认事件执行器
this.defaultEventExecutorGroup.shutdownGracefully();
}
} catch (Exception e) {
log.error("NettyRemotingServer shutdown exception, ", e);
}
if (this.publicExecutor != null) {
try {
// 停止公共线程池
this.publicExecutor.shutdown();
} catch (Exception e) {
log.error("NettyRemotingServer shutdown exception, ", e);
}
}
}
到这里,NameServer 的启动和停止流程就分析完成了。