文章目录
namesrv原理图
名称 | 内容 |
---|---|
HashMap<String/* topic */, List< QueueData>> topicQueueTable | 主题与队列关系,记录一个主题的队列分布在哪些Broker上,每个Broker上存在该主题的队列个数。QueueData队列描述信息 |
HashMap<String/* brokerName */, BrokerData> brokerAddrTable | 存储brokername和broker地址的关系 |
HashMap<String/* brokerAddr */, BrokerLiveInfo> brokerLiveTable | broker最后的心跳相关信息, brokerLiveTable,当前存活的 Broker,该信息不是实时的,NameServer 每10S扫描一次所有的 broker,根据心跳包的时间得知 broker的状态,该机制也是导致当一个 Broker 进程假死后,消息生产者无法立即感知,可能继续向其发送消息,导致失败(非高可用 |
HashMap<String/* clusterName /, Set<String/ brokerName */>> clusterAddrTable | 集群地址表 broker 集群信息,每个集群包含哪些 Broker |
HashMap<String/* brokerAddr /, List/ Filter Server */> filterServerTable | 过滤相关,是RocketMQ的一种服务端过滤方式,基本不在使用,高版本会废弃 |
1 NameServer是一个轻量级的注册中心
2 采用定时注册,心跳续期,定时删除过期信息进行维护
3 主要由broker注册信息,生产者和消费者获取消息
4 核心信息存储在RouteInfoManager中
源码分析一NamesrvStartup启动nameserver
NamesrvStartup 包装NamesrvController的启动,NamesrvStartup与启动脚本配合,一般不会将核心类直接暴露给脚本文件,而是在起一个脚本类完成脚本启动的控制
- 创建NamesrvController
- start启动controller
public static NamesrvController main0(String[] args) {
...... 删除其他代码
创建NamesrvController
NamesrvController controller = createNamesrvController(args);
启动nameserver
start(controller);
return controller;
}
createNamesrvController创建nameserver
- 通过外部配置[文件,命令行]初始化通信配置NettyServerConfig和nameserver相关配置NamesrvConfig
- 创建NamesrvController
public static NamesrvController createNamesrvController(String[] args) throws IOException, JoranException {
...... 删除其他代码
step-1: 创建name server config 配置
final NamesrvConfig namesrvConfig = new NamesrvConfig();
step-2: 创建netty server config
final NettyServerConfig nettyServerConfig = new NettyServerConfig();
nettyServerConfig.setListenPort(9876);
...... 删除代码: 命令行读取属性配置文件位置 加载properties到NamesrvConfig,NettyServerConfig
...... 删除代码: 命令行读取属性配置键值对到NamesrvConfig,NettyServerConfig
......删除代码: 通过控制台指定属性配置文件位置
step-3: 创建NamesrvController
final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig);
...... 删除其他配置
return controller;
}
- 创建NamesrvController的核心是创建routeInfoManager
- routeInfoManager接收broker注册,向生产者消费者提供注册信息
public NamesrvController(NamesrvConfig namesrvConfig, NettyServerConfig nettyServerConfig) {
赋值配置到属性
this.namesrvConfig = namesrvConfig;
this.nettyServerConfig = nettyServerConfig;
初始化键值对配置管理类
this.kvConfigManager = new KVConfigManager(this);
核心: 构建管理对象
this.routeInfoManager = new RouteInfoManager();
可以说是通道在发送异常时的回调方法(Nameserver与 Broker的连接通道
在关闭、通道发送异常、通道空闲时),在上述数据结构中移除已宕机的 Broker
this.brokerHousekeepingService = new BrokerHousekeepingService(this);
创建配置类
this.configuration = new Configuration(
log,
this.namesrvConfig, this.nettyServerConfig
);
设置持久化的相关位置
this.configuration.setStorePathFromConfig(this.namesrvConfig, "configStorePath");
}
start启动nameserver
- 加载配置,创建心跳续期剔除任务
- [broker每30秒上报相关信息]
- [nameserver每10秒执行清除任务]
- [清除任务:超过120秒还没有续期消息,则被清除]
- 设置关闭时候的资源释放
- 启动controller[启动netty通信]
public static NamesrvController start(final NamesrvController controller) throws Exception {
step-1: 加载配置 配置netty相关 创建心跳续期剔除任务
boolean initResult = controller.initialize();
step-2 :设置关闭钩子
Runtime.getRuntime().addShutdownHook(new ShutdownHookThread(log, new Callable<Void>() {
@Override
public Void call() throws Exception {
controller.shutdown();
return null;
}
}));
step-3: 启动netty通信,赋予nameserver与broker以及生产者消费者通信能力
controller.start();
return controller;
}
controller.initialize
- 构建 netty server[主要是epoll模型还是普通模型的选择]
- 注册消息处理器,netty的收发消息最终交由DefaultRequestProcessor处理
- 启动清除任务,扫描不活跃的broker信息并进行清除[每10秒清除broker120秒还没有进行上报的信息,broker端每30秒进行注册]
public boolean initialize() {
...... 删除kv处理代码
构建 netty server[主要是epoll模型还是普通模型的选择]
this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);
step-1: 注册消息处理器,netty的收发消息最终交由DefaultRequestProcessor处理
this.registerProcessor();
step-2: 创建定时线程池任务
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
每10秒,扫描本地缓存信息;如果心跳信息长时间(120s)没到达,剔除rocketmq信息
@Override
public void run() {
NamesrvController.this.routeInfoManager.scanNotActiveBroker();
}
}, 5, 10, TimeUnit.SECONDS);
...... 删除Ssl相关
return true;
}
namesrvController.start
- 启动netty组件
public void start() throws Exception {
this.remotingServer.start();
if (this.fileWatchService != null) {
this.fileWatchService.start();
}
}
- 启动netty的核心是将构建消息收发的处理器链
- 处理器链将消息收发的处理交由DefaultRequestProcessor处理
public void start() {
...... 删除其他代码
设置一些可共享的handler
prepareSharableHandlers();
ServerBootstrap childHandler =
this.serverBootstrap.group(this.eventLoopGroupBoss, this.eventLoopGroupSelector)
.channel(useEpoll() ? EpollServerSocketChannel.class : NioServerSocketChannel.class)
...... 删除一些通道性质代码
.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将消息的收发交由DefaultRequestProcessor处理
serverHandler
);
}
});
netty内存分配策略: 采用池化的堆外内存-参见伙伴分配算法 降低内存碎片,提高利用率等 这里不再过多叙述
if (nettyServerConfig.isServerPooledByteBufAllocatorEnable()) {
childHandler.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
}
try {
ChannelFuture sync = this.serverBootstrap.bind().sync();
} catch (InterruptedException e1) {
throw new RuntimeException("this.serverBootstrap.bind().sync() InterruptedException", e1);
}
}
总结
- nameserver维护了一个RouteInfoManager,用于存储broker注册信息
- nameserver通过DefaultRequestProcessor处理请求,包括broker的注册,mqclient拉取
- nameserver启动定时任务,每10秒清除120秒没有续期的broker信息
扩展点一DefaultRequestProcessor
Processor具有处理broker注册,取消注册能力
Processor具有提供Mqclient(生产者,消费者)拉取信息能力
broker注册与发现实质为读写RouteIngoManager的信息
public class DefaultRequestProcessor extends AsyncNettyRequestProcessor implements NettyRequestProcessor {
@Override
public RemotingCommand processRequest(ChannelHandlerContext ctx,
RemotingCommand request) throws RemotingCommandException {
switch (request.getCode()) {
...... 删除其他请求
broker向nameserver注册
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);
}
broker向nameserver取消注册
case RequestCode.UNREGISTER_BROKER:
return this.unregisterBroker(ctx, request);
客户端获取路由topic信息:服务发现
case RequestCode.GET_ROUTEINTO_BY_TOPIC:
return this.getRouteInfoByTopic(ctx, request);
...... 删除大量其他请求代码
default:
break;
}
return null;
}
}
扩展点一scanNotActiveBroker
- 如果最后一次注册的时间戳超过120秒,则从RouteInfoManager.brokerLiveTable清除相关信息
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();
BROKER_CHANNEL_EXPIRED_TIME = 120 秒
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());
}
}
}