前言
核心目录结构
- broker:broker模块(broker启动进程)
- client:消息客户端,包含消息生产者、消息消费者相关类
- common:公共包
- dev:开发者信息(非源代码)
- distribution:部署实例文件夹(非源代码)
- example:RocketMQ示例代码
- filter:消息过滤相关基础类
- filter:消息过滤服务器实现相关类(Filter启动进程)
- logappender:日志实现相关类
- namesrv:NameServer实现相关类(NameServer启动进程)
- openmessageing:消息开放标准,正在制定中
- remoting:远程通信模块,基于Netty
- srvutil:服务器工具类
- store:消息存储实现相关类
- style:checkstyle相关实现
- test:测试相关类
- tools:工具类,监控命令相关实现类
设计理念
RocketMQ设计基于主题的发布与订阅模式,核心功能包括消息发送、消息存储(Broker)、消息消费,整体设计追求简单与性能第一,主要体现在如下三个方面:
-
NameServer设计极其简单,使用自研NameServer实现元数据的管理(Topic路由信息等),Topic路由信息追求最终一致性可容忍分钟级的不一致,所以,RocketMQ的NameServer集群互不通信,降低了实现的复杂度和对网络的要求,提高性能
-
高效的IO存储机制。追求消息发送的高吞吐量,RocketMQ的消息存储文件设计成文件组的概念,组内单个文件大小固定,方便引入内存映射机制,消息存储基于顺序写,极大地提升了消息的写性能,为了兼顾消息消费与消息查找,引入了消息消费队列文件与索引文件
-
容忍存在设计缺陷,适当将某些工作下放给RocketMQ使用者。只保证消息被消费者消费,但设计上允许消息被重复消费,消息重复问题由消费者在消息消费时实现幂等
RocketMQ一个Topic拥有多个消息队列,一个Broker为每一个主题默认创建4个读队列4个写队列。多个Broker组成一个集群,BrokerName由相同的多台Broker组成Master-Slave架构,brokerId为0代表Master,大于0表示Slave。BrokerLiveInfo中的lastUpdateTimestamp存储上次收到Broker心跳包的时间
1.路由中心NameServer
Broker消息服务器在启动时向所有NameServer注册,消息生产者(Producer)在发送消息之前先从NameServer获取Broker服务器地址列表,根据负载算法从列表中选择一台消息服务器进行消息发送。NameServer与每台Broker服务器保持长连接,并间隔10s检测Broker是否存活,如果检测到Broker宕机,则从路由注册表中将其移除,但是路由并不会马上通知消息生产者
- 目的:降低NameServer实现的复杂度,在消息发送端提供容错机制来保证消息发送的高可用性
- NameServer集群彼此之间互不通信,所以NameServer服务器之间在某一时刻的数据并不会完全相同,简单高效,可以更轻松地容忍节点故障
1.1启动流程
- 首先解析配置文件,需要填充NameServerConfig、NettyServerConfig属性值
- 根据启动属性创建NamesrvController实例,并初始化该实例,NameServerController实例为NameServer核心控制器
- 注册JVM钩子函数并启动服务器,以便监听Broker、消息生产者的网络要求
public static NamesrvController main0(String[] args) {
try {
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;
}
public static NamesrvController createNamesrvController(String[] args) throws IOException, JoranException {
// NameServer业务参数
final NamesrvConfig namesrvConfig = new NamesrvConfig();
// NameServer网络参数,使用Netty
// 在启动NameServer时会使用Util类检查配置参数,如 -c、--属性名 属性值(--listenPort 9876)
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);
MixAll.properties2Object(properties, namesrvConfig);
MixAll.properties2Object(properties, nettyServerConfig);
namesrvConfig.setConfigStorePath(file);
System.out.printf("load config properties file OK, %s%n", file);
in.close();
}
}
if (commandLine.hasOption('p')) {
InternalLogger console = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_CONSOLE_NAME);
MixAll.printObjectProperties(console, namesrvConfig);
MixAll.printObjectProperties(console, nettyServerConfig);
System.exit(0);
}
MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), namesrvConfig);
}
public static NamesrvController start(final NamesrvController controller) throws Exception {
if (null == controller) {
throw new IllegalArgumentException("NamesrvController is null");
}
// 初始化NamesrvController
boolean initResult = controller.initialize();
if (!initResult) {
controller.shutdown();
System.exit(-3);
}
// 注册JVM钩子函数并启动服务器,以便监听Broker、消息生产者的网络要求
// 线程池优雅关闭的方式:注册一个JVM钩子函数,在JVM进程关闭之前,先将线程池关闭,及时释放资源
Runtime.getRuntime().addShutdownHook(new ShutdownHookThread(log, (Callable<Void>) () -> {
controller.shutdown();
return null;
}));
controller.start();
return controller;
}
NameServerConfig属性
public class NamesrvConfig {
private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);
// 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默认配置文件路径,不生效。可通过参数-c配置
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属性
public class NettyServerConfig implements Cloneable {
//NameServer监听端口,默认初始化为9876
private int listenPort = 8888;
// Netty业务线程池线程个数
private int serverWorkerThreads = 8;
/**
* Netty public任务线程池线程个数
* Netty网络设计,根据业务类型会创建不同的线程池,比如处理消息发送、消息消费、心跳检测等
* 如果该业务类型(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;
private int writeBufferHighWaterMark = NettySystemConfig.writeBufferHighWaterMark;
private int writeBufferLowWaterMark = NettySystemConfig.writeBufferLowWaterMark;
private int serverSocketBacklog = NettySystemConfig.socketBacklog;
// ByteBuffer是否开启缓存,建议开启
private boolean serverPooledByteBufAllocatorEnable = true;
// 是否启用Epoll IO模型,Linux环境建议开启
private boolean useEpollNativeSelector = false;
}
NamesrvController#Initialize代码片段
public boolean initialize() {
// 加载KV配置
this.kvConfigManager.load();
// 创建NettyServer网络处理对象
this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);
this.remotingExecutor =
Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), new ThreadFactoryImpl("RemotingExecutorThread_"));
this.registerProcessor();
/**
* 创建两个定时任务,即心跳检测
* 定时任务1:NameServer每隔10s扫描一次Broker,移除处于不激活状态的Broker
* 定时任务2:nameServer每隔10分钟打印一次KV配置
*/
this.scheduledExecutorService.scheduleAtFixedRate(NamesrvController.this.routeInfoManager::scanNotActiveBroker, 5, 10, TimeUnit.SECONDS);
this.scheduledExecutorService.scheduleAtFixedRate(NamesrvController.this.kvConfigManager::printAllPeriodically, 1, 10, TimeUnit.MINUTES);
return true;
}
1.2路由注册、故障剔除
NameServer主要作用是为消息生产者和消息消费者提供关于主题Topic的路由信息,那么NameServer需要存储路由的基础信息,还要能够管理Broker节点,包括路由注册、路由剔除等功能。
路由元信息(五张表!)
RocketMQ一个Topic拥有多个消息队列,一个Broker为每一个主题默认创建4个读队列4个写队列。多个Broker组成一个集群,BrokerName由相同的多台Broker组成Master-Slave架构,brokerId为0代表Master,大于0表示Slave。BrokerLiveInfo中的lastUpdateTimestamp存储上次收到Broker心跳包的时间
public class RouteInfoManager {
private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);
private final static long BROKER_CHANNEL_EXPIRED_TIME = 1000 * 60 * 2;
// 使用到了读写锁
private final ReadWriteLock lock = new ReentrantReadWriteLock();
// Topic消息队列路由信息,消息发送时根据路由表进行负载均衡
private final HashMap<String/* topic */, Map<String /* brokerName */ , QueueData>> topicQueueTable;
// Broker基础信息,包含brokerName、所属集群名称、主备Broker地址
private final HashMap<String/* brokerName */, BrokerData> brokerAddrTable;
// Broker集群信息,存储集群中所有Broker名称
private final HashMap<String/* clusterName */, Set<String/* brokerName */>> clusterAddrTable;
// Broker状态信息。NameServer每次收到心跳包时会替换该信息
private final HashMap<String/* brokerAddr */, BrokerLiveInfo> brokerLiveTable;
// Broker上的FilterServer列表,用于类模式消息过滤
private final HashMap<String/* brokerAddr */, List<String>/* Filter Server */> filterServerTable;
}
路由注册
RocketMQ的路由注册是通过Broker与NameServer的心跳功能实现的。Broker启动时向集群中所有的NameServer发送心跳语句,设置定时任务每隔30s向集群中所有的NameServer发送心跳包,NameServer收到Broker心跳包时会更新brokerLiveTable缓存中BrokerLiveInfo的lastUpdateTimestamp,然后NameServer每隔10s扫描brokerLiveTable,如果连接120s没有收到心跳包,NameServer将移除该Broker的路由信息同时关闭Socket连接
Broker发送心跳包(每隔30s)
将Broker相关信息发送到NameServerList中的每个NameServer中
-
发送心跳包的具体逻辑:首先封装请求包头(Header)
-
brokerAddr:broker地址
-
brokerId:brokerId,0:Maseter;大于0:Slave
-
brokerName:broker名称,主从brokerName一致
-
clusterName:集群名称
-
haServerAddr:master地址,初次请求时该值为空,slave向NameServer注册后返回
-
requestBody:
- filterServerList:消息过滤服务器列表
- topicConfigWrapper:主题配置
// BrokerController#start
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
BrokerController.this.registerBrokerAll(true, false, brokerConfig.isForceRegister());
} catch (Throwable e) {
log.error("registerBrokerAll Exception", e);
}
} // getRegisterNameServerPeriod():1000*30
}, 1000 * 10, Math.max(10000, Math.min(brokerConfig.getRegisterNameServerPeriod(), 60000)), TimeUnit.MILLISECONDS);
---------------------------------------------------------------------------------------------------------
// 内部会调用BrokerOuterAPI#registerBrokerAll
// 方法主要是遍历所有NameServer列表,Broker消息服务器会依次向NameServer发送心跳包
List<String> nameServerAddressList = this.remotingClient.getNameServerAddressList();
if (nameServerAddressList != null && nameServerAddressList.size() > 0) {
final RegisterBrokerRequestHeader requestHeader = new RegisterBrokerRequestHeader();
requestHeader.setBrokerAddr(brokerAddr);
requestHeader.setBrokerId(brokerId);
requestHeader.setBrokerName(brokerName);
requestHeader.setClusterName(clusterName);
requestHeader.setHaServerAddr(haServerAddr);
requestHeader.setCompressed(compressed);
RegisterBrokerBody requestBody = new RegisterBrokerBody();
requestBody.setTopicConfigSerializeWrapper(topicConfigWrapper);
requestBody.setFilterServerList(filterServerList);
final byte[] body = requestBody.encode(compressed);
final int bodyCrc32 = UtilAll.crc32(body);
requestHeader.setBodyCrc32(bodyCrc32);
final CountDownLatch countDownLatch = new CountDownLatch(nameServerAddressList.size());
// 遍历所有NameServer列表
for (final String namesrvAddr : nameServerAddressList) {
brokerOuterExecutor.execute(() -> {
try {
// 分别向NameServer注册
RegisterBrokerResult result = registerBroker(namesrvAddr, oneway, timeoutMills, requestHeader, body);
if (result != null) {
registerBrokerResultList.add(result);
}
log.info("register broker[{}]to name server {} OK", brokerId, namesrvAddr);
} catch (Exception e) {
log.warn("registerBroker Exception, {}", namesrvAddr, e);
} finally {
countDownLatch.countDown();
}
});
}
---------------------------------------------------------------------------------------------------------
// 接着会调用BrokerOuterAPI#registerBroker(网络发送代码)
private RegisterBrokerResult registerBroker(
final String namesrvAddr,
final boolean oneway,
final int timeoutMills,
final RegisterBrokerRequestHeader requestHeader,
final byte[] body
) throws RemotingCommandException, MQBrokerException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException,
InterruptedException {
// 利用初始化好的请求头对象创建一个远程命令对象
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.REGISTER_BROKER, requestHeader);
request.setBody(body);
if (oneway) {
try {
this.remotingClient.invokeOneway(namesrvAddr, request, timeoutMills);
} catch (RemotingTooMuchRequestException e) {
// Ignore
}
return null;
}
// 在 NettyRemotingClient 类下,基于Netty进行网络传输,每一个请求,RocketMQ都会定义一个RequestCode,然后在服务端对应相应的网络处理器(processor包下)
RemotingCommand response = this.remotingClient.invokeSync(namesrvAddr, request, timeoutMills);
NameServer处理心跳包
本质上是维护RouteInfoManager中的那五张表的信息
- Step1:路由注册需要加写锁,防止并发修改RouteInfoManager中的路由表。首先判断Broker所属集群是否存在,如果不存在,则创建,然后将broker名加入到集群Broker集合中
- Step2:维护BrokerData信息,首先从brokerAddrTable根据BrokerName尝试获取Broker信息,如果不存在,则新建BrokerData并放入到brokerAddrTable,registerFirst设置为true;如果存在,registerFirst设置为false,表示非第一次注册。
- Step3:如果Broker为Maseter,并且Broker Topic配置信息发生变化或者是初次注册,则需要创建或更新Topic路由元数据,填充topicQueueTable,其实就是为默认主题自动注册路由信息,其中包含MixAll.DEFAULT_TOPIC的路由信息。当消息生产者发送主题时,如果该主题未创建并且BrokerConfig的autoCreateTopicEnable为true时,将返回MixAll.DEFAULT_TOPIC的路由信息.
- Step4:更新BrokerLiveInfo,存活Broker信息表,BrokerLiveInfo是执行路由删除的重要依据
- Step5:注册Broker的过滤器Server地址列表,一个Broker上会关联多个FilterServer消息过滤服务器。如果此Broker为从节点,则需要查找该Broker的Master节点信息,并更新对应的masterAddr属性。
org.apache.rocketmq.namesrv.processor.DefaultRequestProcessor网络处理器解析请求类型,如果请求类型为RequestCode.REGISTER_BROKER,则请求最终转发到RouteInfoManager#registerBroker
// RouteInfoManager#registerBroker
// clusterAddrTable维护
// Step1:路由注册需要加写锁,防止并发修改RouteInfoManager中的路由表。
// 首先判断Broker所属集群是否存在,如果不存在,则创建,然后将broker名加入到集群Broker集合中
this.lock.writeLock().lockInterruptibly();
Set<String> brokerNames = this.clusterAddrTable.computeIfAbsent(clusterName, k -> new HashSet<>());
brokerNames.add(brokerName);
// brokerAddrTable 维护
// Step2:维护BrokerData信息,首先从brokerAddrTable根据BrokerName尝试获取Broker信息,
// 如果不存在,则新建BrokerData并放入到brokerAddrTable,registerFirst设置为true;
// 如果存在,registerFirst设置为false,表示非第一次注册。
BrokerData brokerData = this.brokerAddrTable.get(brokerName);
if (null == brokerData) {
registerFirst = true;
brokerData = new BrokerData(clusterName, brokerName, new HashMap<>());
this.brokerAddrTable.put(brokerName, brokerData);
}
String oldAddr = brokerData.getBrokerAddrs().put(brokerId, brokerAddr);
registerFirst = registerFirst || (null == oldAddr);
// topicQueueTable 维护
// Step3:如果Broker为Maseter,并且Broker Topic配置信息发生变化或者是初次注册,则需要创建或更新Topic路由元数据,填充topicQueueTable,其实就是为默认主题自动注册路由信息,其中包含MixAll.DEFAULT_TOPIC的路由信息。当消息生产者发送主题时,如果该主题未创建并且BrokerConfig的autoCreateTopicEnable为true时,将返回MixAll.DEFAULT_TOPIC的路由信息.
if (null != topicConfigWrapper && MixAll.MASTER_ID == brokerId) {
if (this.isBrokerTopicConfigChanged(brokerAddr, topicConfigWrapper.getDataVersion()) || registerFirst) {
ConcurrentMap<String, TopicConfig> tcTable = topicConfigWrapper.getTopicConfigTable();
if (tcTable != null) {
for (Map.Entry<String, TopicConfig> entry : tcTable.entrySet()) {
this.createAndUpdateQueueData(brokerName, entry.getValue());
}
}
}
}
// 根据TopicConfig创建QueueData数据结构,然后更新topicQueueTable
private void createAndUpdateQueueData(final String brokerName, final TopicConfig topicConfig) {
QueueData queueData = new QueueData();
queueData.setBrokerName(brokerName);
queueData.setWriteQueueNums(topicConfig.getWriteQueueNums());
queueData.setReadQueueNums(topicConfig.getReadQueueNums());
queueData.setPerm(topicConfig.getPerm());
queueData.setTopicSysFlag(topicConfig.getTopicSysFlag());
Map<String, QueueData> queueDataMap = this.topicQueueTable.get(topicConfig.getTopicName());
if (null == queueDataMap) {
queueDataMap = new HashMap<>();
queueDataMap.put(queueData.getBrokerName(), queueData);
this.topicQueueTable.put(topicConfig.getTopicName(), queueDataMap);
log.info("new topic registered, {} {}", topicConfig.getTopicName(), queueData);
} else {
QueueData old = queueDataMap.put(queueData.getBrokerName(), queueData);
if (old != null && !old.equals(queueData)) {
log.info("topic changed, {} OLD: {} NEW: {}", topicConfig.getTopicName(), old,
queueData);
}
}
}
// Step4:更新BrokerLiveInfo,存活Broker信息表,BrokerLiveInfo是执行路由删除的重要依据
BrokerLiveInfo prevBrokerLiveInfo = this.brokerLiveTable.put(brokerAddr,
new BrokerLiveInfo(
System.currentTimeMillis(),
topicConfigWrapper.getDataVersion(),
channel,
haServerAddr));
if (null == prevBrokerLiveInfo) {
log.info("new broker registered, {} HAServer: {}", brokerAddr, haServerAddr);
}
// Step5:注册Broker的过滤器Server地址列表,一个Broker上会关联多个FilterServer消息过滤服务器。如果此Broker为从节点,则需要查找该Broker的Master节点信息,并更新对应的masterAddr属性。
if (filterServerList != null) {
if (filterServerList.isEmpty()) {
this.filterServerTable.remove(brokerAddr);
} else {
this.filterServerTable.put(brokerAddr, filterServerList);
}
}
if (MixAll.MASTER_ID != brokerId) {
String masterAddr = brokerData.getBrokerAddrs().get(MixAll.MASTER_ID);
if (masterAddr != null) {
BrokerLiveInfo brokerLiveInfo = this.brokerLiveTable.get(masterAddr);
if (brokerLiveInfo != null) {
result.setHaServerAddr(brokerLiveInfo.getHaServerAddr());
result.setMasterAddr(masterAddr);
}
}
}
设计亮点
NameServer与Broker保持长连接,Broker状态存储在brokerLiveTable中,NameServer每收到一个心跳包,将更新brokerLiveTable中关于Broker的状态信息以及路由表(topicQueueTable、brokerAddrTable、brokerLiveTable、filterServerTable)。
更新上述路由表(HashTable)使用了锁粒度较少的读写锁,允许多个消息发送者(Producr)并发读,保证消息发送时的高并发。但同一时刻NameServer只处理一个Broker心跳包,多个心跳包请求串行执行。
路由删除
Broker每隔30s向NameServer发送一个心跳包,心跳包中包含BrokerId、Broker地址、Broker名称、Broker所属集群名称、Broker关联的FilterServer列表。如果Broker宕机,NameServer会剔除失效的Broker
NameServer每隔10s会扫描brokerLiveTable状态表,如果BrokerLive的lastUpdateTimestamp的时间戳距当前时间超过120s,则认为Broker失效,移除该Broker,关闭与Broker连接,并同时更新topicQueueTable、brokerAddrTable、brokerLiveTable、filterServerTable
RocketMQ有两个触发点来触发路由删除
- NameServer定时扫描brokerLiveTable检测上次心跳包与当前系统时间的时间差,如果时间戳大于120s,则需要移除该Broker信息
- Broker在正常被关闭的情况下,会执行unregisterBroker指令
// RouteInfoManager#scanNotActiveBroker
public int scanNotActiveBroker() {
int removeCount = 0;
Iterator<Entry<String, BrokerLiveInfo>> it = this.brokerLiveTable.entrySet().iterator();
while (it.hasNext()) {
Entry<String, BrokerLiveInfo> next = it.next();
long last = next.getValue().getLastUpdateTimestamp();
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());
removeCount++;
}
}
return removeCount;
}
// onChannelDestroy方法,关闭channel,删除与该Broker相关的路由信息,需要加写锁
this.lock.writeLock().lockInterruptibly();
this.brokerLiveTable.remove(brokerAddrFound);
this.filterServerTable.remove(brokerAddrFound);
- Step1:申请写锁,根据brokerAddress从brokerLiveTable、filterServerTable移除
- Step2:维护brokerAddrTable,遍历brokerAddrTable,找到要删除的Broker,从BrokerData中移除,如果移除后为空,则从brokerAddrTable中移除该brokerName对应的条目
- Step3:根据BrokerName,从clusterAddrTable中找到Broker并从集群中移除
- Step4:根据brokerName,遍历所有主题的队列,如果队列中包含了当前Broker的队列,则移除,如果topic只包含待移除Broker的队列,从路由表中删除该topic
- Step5:释放写锁,完成路由删除
路由发现
RocketMQ路由是非实时的,当Topic路由发生变化后,NameServer不主动推送给客户端,而是由客户端定时拉取主题最新的路由
private String orderTopicConf; // 顺序消息配置内容,来自于KVConfig
private List<QueueData> queueDatas; // topic队列元数据
private List<BrokerData> brokerDatas; // topic分布的broker元数据
// broker上过滤服务器地址列表
private HashMap<String/* brokerAddr */, List<String>/* Filter Server */> filterServerTable;
NameServer路由发现实现类:DefaultRequestProcessor#getRouteInfoByTopic
- Step1: 调用RouteInfoManager的方法,从路由表topicQueueTable、brokerAddrTable、filterServerTable中分别填充TopicRouteData中的List、List和filterServer地址表
- Step2:如果找到主题对应的路由信息并且该主题为顺序消息,则从NameServer KVConfig中获取关于顺序消息相关的配置填充路由信息。如果找不到路由信息CODE则使用ResponseCode.TOPIC_NOT_EXIST,表示没有对应的路由
public RemotingCommand getRouteInfoByTopic(ChannelHandlerContext ctx,
RemotingCommand request) throws RemotingCommandException {
final RemotingCommand response = RemotingCommand.createResponseCommand(null);
final GetRouteInfoRequestHeader requestHeader =
(GetRouteInfoRequestHeader) request.decodeCommandCustomHeader(GetRouteInfoRequestHeader.class);
/**
* topicRouteData.setBrokerDatas(brokerDataList);
* topicRouteData.setFilterServerTable(filterServerMap);
* topicRouteData.setQueueDatas(new ArrayList<>(queueDataMap.values()));
*/
TopicRouteData topicRouteData = this.namesrvController.getRouteInfoManager().pickupTopicRouteData(requestHeader.getTopic());
if (topicRouteData != null) {
if (this.namesrvController.getNamesrvConfig().isOrderMessageEnable()) {
String orderTopicConf =
this.namesrvController.getKvConfigManager().getKVConfig(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG,
requestHeader.getTopic());
topicRouteData.setOrderTopicConf(orderTopicConf);
}
byte[] content;
Boolean standardJsonOnly = requestHeader.getAcceptStandardJsonOnly();
if (request.getVersion() >= Version.V4_9_4.ordinal() || (null != standardJsonOnly && standardJsonOnly)) {
content = topicRouteData.encode(SerializerFeature.BrowserCompatible,
SerializerFeature.QuoteFieldNames, SerializerFeature.SkipTransientField,
SerializerFeature.MapSortField);
} else {
content = RemotingSerializable.encode(topicRouteData);
}
response.setBody(content);
response.setCode(ResponseCode.SUCCESS);
response.setRemark(null);
return response;
}
response.setCode(ResponseCode.TOPIC_NOT_EXIST);
response.setRemark("No topic route info in name server for the topic: " + requestHeader.getTopic()
+ FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL));
return response;
}
1.3总结
存在问题:NameServer需要等Broker失效至少120s才能将Broker从路由表中移除,如果在Broker故障期间,消息生产者Producer根据主题获取到的路由信息包含已经宕机的Broker,会导致消息发送失败,怎么解决?