本文已参与[新人创作礼]活动,一路开启掘金创作之路。
基于rocketmq-4.9.0 版本分析rocketmq
1.NameServer是什么?
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的路由的信息
2.NameServer启动入口类
public class NamesrvStartup {
private static InternalLogger log;
private static Properties properties = null;
private static CommandLine commandLine = null;
public static void main(String[] args) {
main0(args);
}
public static NamesrvController main0(String[] args) {
try {
NamesrvController controller = createNamesrvController(args);
//TODO:NameServer启动
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;
}
知道了入口,那接下来我们就分析下它是如何启动的,启动的过程中都做了什么?从上面的部分代码中,不难看出,它首先就是创建NamesrvController
对象,然后启动(start()
方法),那我们就一步一步走进去看看细节.
3.创建 NamesrvController
对象
不要看代码比较多,其实主要就是创建3个对象,请往后看
public static NamesrvController createNamesrvController(String[] args) throws IOException, JoranException {
System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, Integer.toString(MQVersion.CURRENT_VERSION));
//PackageConflictDetect.detectFastjson();
Options options = ServerUtil.buildCommandlineOptions(new Options());
commandLine = ServerUtil.parseCmdLine("mqnamesrv", args, buildCommandlineOptions(options), new PosixParser());
if (null == commandLine) {
System.exit(-1);
return null;
}
//TODO:创建NamesrvConfig对象
final NamesrvConfig namesrvConfig = new NamesrvConfig();
//TODO: 创建 NettyServerConfig 对象
final NettyServerConfig nettyServerConfig = new NettyServerConfig();
//TODO: 设置固定端口号9876,客户端连接的时候就是 nameserverip:9876
nettyServerConfig.setListenPort(9876);
//TODO: 读取命令行中 -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();
}
}
//TODO: 判断是否有 -p 参数值
if (commandLine.hasOption('p')) {
InternalLogger console = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_CONSOLE_NAME);
MixAll.printObjectProperties(console, namesrvConfig);
MixAll.printObjectProperties(console, nettyServerConfig);
System.exit(0);
}
//TODO:将命令行参数映射到 NamesrvConfig类中
MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), namesrvConfig);
//TODO: 判断是否指定ROCKETMQ_HOME,本地运行时,必须要指定该值
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);
//TODO: 创建 NamesrvController 对象
final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig);
// remember all configs to prevent discard
controller.getConfiguration().registerConfig(properties);
return controller;
}
总结一下就是创建3个对象
- 创建
NamesrvConfig
对象,设置一些配置,比如ROCKETMQ_HOME
public class NamesrvConfig {
private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);
//TODO: 指定ROCKETMQ_HOME,必须,否则本地运行将失败
private String rocketmqHome = System.getProperty(MixAll.ROCKETMQ_HOME_PROPERTY, System.getenv(MixAll.ROCKETMQ_HOME_ENV));
//TODO: 可以在这个路径下创建kvConfig.json文件,它将会读取值
private String kvConfigPath = System.getProperty("user.home") + File.separator + "namesrv" + File.separator + "kvConfig.json";
//TODO: 可以在这个路径下创建namesrv.properties文件,它将会读取值
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
对象,设置固定端口号9876
,生产者和消费者连接的时候通过namesrvip:9876进行连接 - 创建
NamesrvController
对象,并将其两个对象设置进来,除此以外还有一些其他对象
public NamesrvController(NamesrvConfig namesrvConfig, NettyServerConfig nettyServerConfig) {
this.namesrvConfig = namesrvConfig;
this.nettyServerConfig = nettyServerConfig;
//TODO:读取NamesrvConfig中kvConfigPath路径下的文件
this.kvConfigManager = new KVConfigManager(this);
//TODO:这个对象非常重要,nameserver存储的broker信息和路由表信息都保存在这里
this.routeInfoManager = new RouteInfoManager();
this.brokerHousekeepingService = new BrokerHousekeepingService(this);
this.configuration = new Configuration(log, this.namesrvConfig, this.nettyServerConfig);
this.configuration.setStorePathFromConfig(this.namesrvConfig, "configStorePath");
}
4.启动过程(NamesrvController#start()
)
4.1 初始化(NamesrvController#initialize()
)
public boolean initialize() {
//TODO: 读取NamesrvConfig中的指定路径的文件,然后保存在配置表中(Map)
this.kvConfigManager.load();
//TODO:创建远程netty服务,这个很重要,后面还会看到
this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);
//TODO:创建线程池
this.remotingExecutor =
Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), new ThreadFactoryImpl("RemotingExecutorThread_"));
//注册处理器
this.registerProcessor();
//TODO:启动定时任务,扫描不活跃的broker
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
NamesrvController.this.routeInfoManager.scanNotActiveBroker();
}
}, 5, 10, TimeUnit.SECONDS);
//TODO: ....省略部分代码......
return true;
}
4.1.1 加载配置
this.kvConfigManager.load();
在前面创建 NamesrvConfig
对象时,其内部有固定的kvConfigPath
属性,如果存在这个文件,那么KVConfigManager
对象将在这里读取,然后保存在其内部的配置表中configTable
(Map)
4.1.2 创建远程服务NettyRemotingServer
对象
this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);
我们看它的构造函数都做了什么
public NettyRemotingServer(final NettyServerConfig nettyServerConfig,
final ChannelEventListener channelEventListener) {
super(nettyServerConfig.getServerOnewaySemaphoreValue(), nettyServerConfig.getServerAsyncSemaphoreValue());
//TODO:创建ServerBootstrap对象,它是netty的服务端启动类
this.serverBootstrap = new ServerBootstrap();
this.nettyServerConfig = nettyServerConfig;
this.channelEventListener = channelEventListener;
int publicThreadNums = nettyServerConfig.getServerCallbackExecutorThreads();
if (publicThreadNums <= 0) {
publicThreadNums = 4;
}
//TODO:创建一个线程池
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());
}
});
//TODO:这里有一个usePoll的判断,为了减少篇幅,我移除了;无论是否为true,主要都是创建下面两个对象
{
//TODO:负责监听TCP网络连接请求
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()));
}
});
//TODO: 在eventLoopGroupBoss接收到连接请求时,负责将建立好连接的socket注册到selector中去
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()));
}
});
}
//TODO:....省略代码......
}
总结一下创建对象时都做了什么
- 创建netty服务端启动类
ServerBootstrap
- 创建一个线程池
publicExecutor
,他的作用就是一个默认线程池,不过这个线程池在nameserver这里其实并没有怎么使用(在broker启动的时候也会创建NettyRemotingServer
对象,broker在注册处理器时,如果没有指定线程池,那么就会使用这个默认的线程池) eventLoopGroupBoss
和eventLoopGroupSelector
线程组:它俩分别是netty用来处理连接事件与读写事件的线程了,eventLoopGroupBoss
对应的是netty的boss
线程组,eventLoopGroupSelector
对应的是worker
线程组
4.1.3 创建线程池
this.remotingExecutor =
Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), new ThreadFactoryImpl("RemotingExecutorThread_"));
NettyRemotingServer
对象有2个比较重要的方法
void registerProcessor(final int requestCode, final NettyRequestProcessor processor,
final ExecutorService executor);
void registerDefaultProcessor(final NettyRequestProcessor processor, final ExecutorService executor);
不难发现,第三个参数是线程池,nameserver会调用registerDefaultProcessor
方法注册处理器,而使用的线程池就是上面创建的remotingExecutor
(请看步骤4.1.4)
4.1.4 注册处理器
//TODO:核心逻辑就这一行
this.remotingServer.registerDefaultProcessor(new DefaultRequestProcessor(this), this.remotingExecutor);
不难发现,它注册了一个默认的处理器对象DefaultRequestProcessor
,同时指定的线程池就是4.1.3中创建的线程池。
注册逻辑:
public void registerDefaultProcessor(NettyRequestProcessor processor, ExecutorService executor) {
this.defaultRequestProcessor = new Pair<NettyRequestProcessor, ExecutorService>(processor, executor);
}
4.1.5 启动定时任务
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
NamesrvController.this.routeInfoManager.scanNotActiveBroker();
}
}, 5, 10, TimeUnit.SECONDS);
启动定时任务,定时扫描不活跃的broker,然后将其从注册表中移除。
到这里 “初始化”过程就结束了,接下来就是启动了。
4.2 启动
public void start() throws Exception {
this.remotingServer.start();
//TODO:....省略部分代码......
}
调用remotingServer
(NettyRemotingServer
)的start()
方法
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());
}
});
//TODO:创建一些对象
prepareSharableHandlers();
ServerBootstrap childHandler =
//TODO:首先设置 bossGroup和workerGroup
this.serverBootstrap.group(this.eventLoopGroupBoss, this.eventLoopGroupSelector)
//TODO:判断使用 epoll 还是 nio
.channel(useEpoll() ? EpollServerSocketChannel.class : NioServerSocketChannel.class)
//TODO:一些其他选项
.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())
//TODO:绑定ip + 端口
.localAddress(new InetSocketAddress(this.nettyServerConfig.getListenPort()))
//TODO:绑定handler
.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(),
//TODO:处理心跳的handler
new IdleStateHandler(0, 0, nettyServerConfig.getServerChannelMaxIdleTimeSeconds()),
//TODO:处理连接的handler
connectionManageHandler,
//TODO:处理读写请求的handler,在上面prepareSharableHandlers()方法中创建的对象
serverHandler
);
}
});
if (nettyServerConfig.isServerPooledByteBufAllocatorEnable()) {
childHandler.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
}
try {
//TODO: 启动netty服务端
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);
}
//TODO:......省略部分代码........
}
不难发现,这个方法就是真正的在启动netty服务端了,简单总结下关注的点
- 设置bossGroup和workerGroup线程组。这是netty中两个执行任务的循环线程组,bossGroup负责处理
accept事件
,workerGroup负责处理read/write事件
. - 决策使用epoll还是nio channel
- 绑定ip和端口
- 添加handler。如果
Channel
是出现了连接/读/写
等事件,这些事件会经过Pipeline
上的ChannelHandler
上进行流转
HandshakeHandler
: 负责处理握手操作NettyEncoder
:负责处理报文编码和解码IdleStateHandler
:负责处理心跳NettyConnectManageHandler
: 负责处理连接- NettyServerHandler:负责处理读写请求。这个
ChannelHandler
就是用来处理broker
注册消息以及producer
/consumer
获取topic消息的。这个要特别关注下,后面我们会看到。
- 启动
//TODO:netty启动
ChannelFuture sync = this.serverBootstrap.bind().sync();
至此,NamesrvController
对象的start()
方法就执行完毕了。NameServer算了启动起来了,然后就是等待外部连接。
5.总结
本文分析了NameServer的启动过程,整个过程大概可以总结为:
- 创建
NamesrvController
对象,并设置必要的配置类。 - 初始化
NamesrvController
对象,主要是构建NettyRemotingServer
对象(其内部维护了netty服务端启动对象ServerBootstrap
。 - 启动
NamesrvController
对象,就是启动NettyRemotingServer
对象,进一步是启动netty服务端对象ServerBootstrap
,这样就可以进行连接,读写操作了。
NameServer是存储broker信息以及topic路由表信息,那么它存储在哪里了呢?
在最开始创建
NamesrvController
对象时,其内部会创RouteInfoManager
对象,而这个对象就是存储信息的容器,下一章节我们就看到它的存储细节。
限于作者个人水平,文中难免有错误之处,欢迎指正! 勿喷,感谢