本文以RocketMQ-4.9.3分析。
NameServer是一个简易的注册中心,实现了Broker的动态注册与发现功能,提供Producer和Conumser查询Broker的路由信息等。
本文主要对其启动流程进行分析,首先来到namesrv工程下的NamesrvStartup类,main方法里面就两个步骤
- 生成NamesrvController对象
- 启动NamesrvController
public class NamesrvStartup {
public static void main(String[] args) {
main0(args);
}
public static NamesrvController main0(String[] args) {
try {
NamesrvController controller = createNamesrvController(args);
start(controller);
//... 省略不相干的日志
return controller;
} catch (Throwable e) {
e.printStackTrace();
System.exit(-1);
}
return null;
}
}
一. 创建NamesrvController
createNamesrvController 主要做两件事:
- 添加启动快捷命令,通过commons-cli包,添加一些快捷命令例如 -help
- 读取配置,并放入NamesrvController中
public static NamesrvController createNamesrvController(String[] args) throws IOException, JoranException {
System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, Integer.toString(MQVersion.CURRENT_VERSION));
// 在Option中添加help,namesrvAddr快捷命令
Options options = ServerUtil.buildCommandlineOptions(new Options());
// 构建命令行解析器
commandLine = ServerUtil.parseCmdLine("mqnamesrv", args, buildCommandlineOptions(options), new PosixParser());
if (null == commandLine) {
System.exit(-1);
return null;
}
// 业务配置
final NamesrvConfig namesrvConfig = new NamesrvConfig();
// Netty网络配置
final NettyServerConfig nettyServerConfig = new NettyServerConfig();
// 设置监听端口
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);
// 将自定义的配置设置到namesrvConfig
MixAll.properties2Object(properties, namesrvConfig);
// 将自定义的配置设置到nettyServerConfig
MixAll.properties2Object(properties, nettyServerConfig);
// 设置配置存储路径
namesrvConfig.setConfigStorePath(file);
in.close();
}
}
// ... 省略类似配置设置
// 将配置放入NamesrvController对象,并实例化KVConfigManager,RouteInfoManager等对象
final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig);
// ....
return controller;
}
二. start
start方法有三个步骤:
- 初始化
- 启动
- 注册jvm钩子
首先说下注册jvm钩子,这个方法是在jvm销毁前调用,用于关闭各类资源。可自定义一些销毁逻辑,以后自己写中间件也可以如此操作,值得个人借鉴。
public static NamesrvController start(final NamesrvController controller) throws Exception {
// ...省略无关紧要
boolean initResult = controller.initialize();
// ...省略无关紧要
// 注册jvm钩子
Runtime.getRuntime().addShutdownHook(new ShutdownHookThread(log, new Callable<Void>() {
@Override
public Void call() throws Exception {
controller.shutdown();
return null;
}
}));
controller.start();
return controller;
}
1. 初始化
这个方式主要进行初始化操作,然后定义了两个定时任务,一个用于打印配置,一个用于扫描过期的Broker。
public boolean initialize() {
// 加载kv配置信息
this.kvConfigManager.load();
this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);
this.remotingExecutor = Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), new ThreadFactoryImpl("RemotingExecutorThread_"));
// 注册默认的处理器,接收到的请求找不到对应的处理,使用默认的
this.registerProcessor();
// 每隔10秒扫描失效的Broker
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
NamesrvController.this.routeInfoManager.scanNotActiveBroker();
}
}, 5, 10, TimeUnit.SECONDS);
// 定时任务每隔10分钟,打印kv配置
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
NamesrvController.this.kvConfigManager.printAllPeriodically();
}
}, 1, 10, TimeUnit.MINUTES);
// ...省略ssl相关
return true;
}
2. 启动
start方式最终会调用NettyRemotingServer中的同名方法。
主要包含Netty的初始化并启动,然后又建立一个定时器,处理过期未响应的连接。
public void start() {
// 省略不重要的
// 实例一些公共的处理器,用于下面的handshakeHandler,encoder,connectionManageHandler,serverHandler
prepareSharableHandlers();
// 下面就是Netty的常规操作
ServerBootstrap childHandler =
this.serverBootstrap.group(this.eventLoopGroupBoss, this.eventLoopGroupSelector)
.channel(useEpoll() ? EpollServerSocketChannel.class : NioServerSocketChannel.class)
// 已完成三次握手,等待处理的连接,最大等待列表长度
.option(ChannelOption.SO_BACKLOG, nettyServerConfig.getServerSocketBacklog())
// 服务宕机立即重启,防止出现地址占用的情况
.option(ChannelOption.SO_REUSEADDR, true)
// 保存长链接
.option(ChannelOption.SO_KEEPALIVE, false)
// 进行传输时,尽量保证以大块数据传输,将小数据积累到一定程度发送,较少网络交互次数
.childOption(ChannelOption.TCP_NODELAY, true)
.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
);
}
});
// 省略不重要的
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);
}
// 省略不重要的
// 定时任务处理半天不响应的连接
this.timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
try {
NettyRemotingServer.this.scanResponseTable();
} catch (Throwable e) {
log.error("scanResponseTable exception", e);
}
}
}, 1000 * 3, 1000);
}