1. NameServer 解决什么问题
为避免消息服务器因单点故障导致整个系统瘫痪,通常会部署多台消息服务器共同承担消息的存储,那么就会存在几个问题,生产者如何知道将消息发送给哪台消息服务器呢?某台消息服务器重启宕机了,生产者如何在不重启服务器的情况下感知呢?所以RocketMQ 引入NameServer 来解决这些问题
2. NameServer 架构设计

Broker 消息服务器在启动时会向所有NameServer 注册,生产者在发送消息之前会先从NameServer获取Broker 服务器的地址列表,然后根据负载算法从列表中选择一台消息服务器发送消息。NameServer与每台Broker服务器保持长连接,并间隔10s检测Broker是否存活,如果检测道Broker宕机会从NameServer中的注册表中删除该节点,但是路由变化不会立刻通知生产者,这是为了降低NameServer 实现的复杂性,因此需要在消息发送端提供容错机制来保证消息发送的高可用性。
NameServer 本身高可用性可通过部署多台NameServer服务器来实现,但彼此之间互不通信。这样虽然在某一个时刻数据并不会完全相同,但对消息发送不会造成重大影响,无非就是短暂造成消息发送不均衡,这也是RocketMQ NameServer 设计的一个亮点。
消息客户端和NameServer、Broker 的交互设计特点如下:
- Broker 每个30s向NameServer集群的每一台机器发送心跳包,包含自身创建的topic路由等信息。
- 消息客户端每个30s向NameServer更新对应的topic的路由信息
- NameServer 收到Broker 发送的心跳包,会记录时间戳放到brokerLiveTable中,然后每隔10s扫描一次brokerLiveTable,如果在120s以内没有收到心跳包就认为Broker失效,更新topic路由信息,将失效的Broker信息移除
3. NameServer 启动
3.1. 时序图
3.2. 源码分析
NameServer的启动入口类是org.apache.rocketmq.namesrv.NamesrvStartup
public class NamesrvStartup {public static NamesrvController main0(String[] args) {try {// 创建NamesrvControllerNamesrvController controller = createNamesrvController(args);// 启动NamesrvControllerstart(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;}}
3.2.1. 创建NamesrvController
org.apache.rocketmq.namesrv.NamesrvStartup.createNamesrvController() 方法创建NamesrvController
public static NamesrvController createNamesrvController(String[] args) throws IOException, JoranException {// 设置版本号为当前版本号System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, Integer.toString(MQVersion.CURRENT_VERSION));//PackageConflictDetect.detectFastjson();//构造org.apache.commons.cli.Options,并添加-h -n参数,-h参数是打印帮助信息,-n参数是指定namesrvAddrOptions options = ServerUtil.buildCommandlineOptions(new Options());//初始化commandLine,并在options中添加-c -p参数,-c指定nameserver的配置文件路径,-p标识打印配置信息commandLine = ServerUtil.parseCmdLine("mqnamesrv", args, buildCommandlineOptions(options), new PosixParser());if (null == commandLine) {System.exit(-1);return null;}// 创建NamesrvConfig 对象,包含业务参数final NamesrvConfig namesrvConfig = new NamesrvConfig();// 创建NettyServerConfig 对象,包含网络参数final NettyServerConfig nettyServerConfig = new NettyServerConfig();// 指定NamesrvServer 端口,默认9876nettyServerConfig.setListenPort(9876);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();}}//命令行带有-p,说明是打印参数的命令,那么就打印出NamesrvConfig和NettyServerConfig的属性// 在启动NameServer时可以先使用./mqnameserver -c configFile -p打印当前加载的配置属性if (commandLine.hasOption('p')) {InternalLogger console = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_CONSOLE_NAME);MixAll.printObjectProperties(console, namesrvConfig);MixAll.printObjectProperties(console, nettyServerConfig);//打印参数命令不需要启动nameserver服务,只需要打印参数即可System.exit(0);}//解析命令行参数,并加载到namesrvConfig中MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), namesrvConfig);//检查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);}//初始化logback日志工厂,rocketmq默认使用logback作为日志输出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);// 根据namesrvConfig和nettyServerConfig 创建NamesrvController 对象final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig);// remember all configs to prevent discard// 合并配置文件controller.getConfiguration().registerConfig(properties);return controller;}
创建NamesrvController主要分为两步
第一步:通过命令行中获取配置。赋值给NamesrvConfig和NettyServerConfig类 第二步:根据配置类NamesrvConfig和NettyServerConfig构造一个NamesrvController实例
NamesrvConfig 是NameServer的配置信息,NeetyServerConfig是NameServer的网络配置信息
NamesrvConfig的配置信息
public class NamesrvConfig {private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);// 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 String productEnvName = "center";private boolean clusterTest = false;// 支持顺序消息,默认不支持private boolean orderMessageEnable = false;
NettyServerConfig的配置信息
public class NettyServerConfig implements Cloneable {// 默认监听端口,在启动时会被初始化为9876private int listenPort = 8888;// Netty 业务线程池线程个数private int serverWorkerThreads = 8;// Netty public 任务线程池线程个数,Netty 网络会根据业务类型创建不同的线程池,比如处理消息发送、消息消费、心跳检测等。如果该业务类型(RequestCode)未注册线程池,则由public 线程池执行private int serverCallbackExecutorThreads = 0;// I/O线程池个数,主要是NameServer、Broker端解析请求、返回相应线程个数。这类线程主要用于处理网络请求,先解析请求包,然后转发到各业务线程池完成具体的业务操作,最后将结果返回给调用方private int serverSelectorThreads = 3;// send oneway 消息请求的并发度(Broker 端参数)private int serverOnewaySemaphoreValue = 256;private int serverAsyncSemaphoreValue = 64;// 网络链接最大空闲时间,默认为120s,如果链接空闲时间超过该参数设置的值,链接将被关闭private int serverChannelMaxIdleTimeSeconds = 120;// 网络socket 发送缓存区大小,默认为64KBprivate int serverSocketSndBufSize = NettySystemConfig.socketSndbufSize;// 网络socket 接收缓存区大小,默认为64KBprivate int serverSocketRcvBufSize = NettySystemConfig.socketRcvbufSize;private int writeBufferHighWaterMark = NettySystemConfig.writeBufferHighWaterMark;private int writeBufferLowWaterMark = NettySystemConfig.writeBufferLowWaterMark;private int serverSocketBacklog = NettySystemConfig.socketBacklog;// Buffer 是否开启缓存,默认开启private boolean serverPooledByteBufAllocatorEnable = true;/*** make make install*** ../glibc-2.10.1/configure \ --prefix=/usr \ --with-headers=/usr/include \* --host=x86_64-linux-gnu \ --build=x86_64-pc-linux-gnu \ --without-gd*/// 是否启用Epool I/O模型,Linux 环境下建议开启private boolean useEpollNativeSelector = false;// 省略Set/Get}
3.2.2. 初始化NamesrvController
创建好NamesrvController对象后,接下来就是对它进行初始化。org.apache.rocketmq.namesrv.initialize()初始化NamesrvController
public boolean initialize() {// 加载KV配置,创建NettyServer 网络处理对象this.kvConfigManager.load();// 根据nettyServerConfig初始化一个netty服务器//brokerHousekeepingService是在NamesrvController实例化时构造函数里实例化的,该类负责Broker连接事件的处理,实现了ChannelEventListener,主要用来管理RouteInfoManager的brokerLiveTablethis.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);//初始化负责处理Netty网络交互数据的线程池,默认线程数是8个this.remotingExecutor =Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), new ThreadFactoryImpl("RemotingExecutorThread_"));//注册Netty服务端业务处理逻辑,如果开启了clusterTest,那么注册的请求处理类是ClusterTestRequestProcessor,否则请求处理类是DefaultRequestProcessorthis.registerProcessor();// 定时任务(心跳检测)每隔10s扫描一次Broker,移除处于未激活状态的Brokerthis.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {NamesrvController.this.routeInfoManager.scanNotActiveBroker();}}, 5, 10, TimeUnit.SECONDS);// 定时任务(心跳检测)每隔10min 打印一次KV 配置this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {NamesrvController.this.kvConfigManager.printAllPeriodically();}}, 1, 10, TimeUnit.MINUTES);if (TlsSystemConfig.tlsMode != TlsMode.DISABLED) {// Register a listener to reload SslContexttry {fileWatchService = new FileWatchService(new String[] {TlsSystemConfig.tlsServerCertPath,TlsSystemConfig.tlsServerKeyPath,TlsSystemConfig.tlsServerTrustCertPath},new FileWatchService.Listener() {boolean certChanged, keyChanged = false;@Overridepublic 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;}
4. 心跳检测(路由删除)
4.1. 时序图
4.2. 源码分析
心跳检测的源码入口org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#scanNotActiveBroker();
每隔10s执行一次,遍历brokerLiveInfo路由表,HashMap结构。检测BrokerLiveInfo的LastUpdateTimestamp上次收到的心跳包时间,如果超过120s,就认为Broker不可用,然后从brokerLiveInfo路由表中移除该Broker。
public void scanNotActiveBroker() {// 遍历brokerLiveTable,这是Broker 状态信息Iterator<Entry<String, BrokerLiveInfo>> it = this.brokerLiveTable.entrySet().iterator();// 遍历Broker 信息while (it.hasNext()) {Entry<String, BrokerLiveInfo> next = it.next();// Broker 最后一次注册的心跳时间long last = next.getValue().getLastUpdateTimestamp();// 最后一次注册时间大于120s,从brokerLiveTable 移除Brokerif ((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());}}}
从brokerLiveTable 移除Broker后,调用onChannelDestroy()删除与Broker相关的路由信息
public void onChannelDestroy(String remoteAddr, Channel channel) {String brokerAddrFound = null;if (channel != null) {try {try {// 加锁this.lock.readLock().lockInterruptibly();Iterator<Entry<String, BrokerLiveInfo>> itBrokerLiveTable =this.brokerLiveTable.entrySet().iterator();// 对比channel 是否一样while (itBrokerLiveTable.hasNext()) {Entry<String, BrokerLiveInfo> entry = itBrokerLiveTable.next();if (entry.getValue().getChannel() == channel) {brokerAddrFound = entry.getKey();break;}}} finally {this.lock.readLock().unlock();}} catch (Exception e) {log.error("onChannelDestroy Exception", e);}}if (null == brokerAddrFound) {brokerAddrFound = remoteAddr;} else {log.info("the broker's channel destroyed, {}, clean it's data structure at once", brokerAddrFound);}if (brokerAddrFound != null && brokerAddrFound.length() > 0) {try {try {// Step1:申请写锁,根据brokerLiveTable、filterServerTable中移除Broker相关信息this.lock.writeLock().lockInterruptibly();// 从brokerLiveTable删除Brokerthis.brokerLiveTable.remove(brokerAddrFound);// 从filterServerTable 删除Brokerthis.filterServerTable.remove(brokerAddrFound);// Broker名称String brokerNameFound = null;// Broker 删除标志位,默认没删除boolean removeBrokerName = false;// Step2:维护brokerAddrTable// 遍历brokerAddTable,从BrokerData的HashMap中找到具体的Broker,从BrokerData中将其移除。如果移除后BrokerData中不在包含其他Broker// 就在brokerAddrTable中移除该brokerName对应的条目Iterator<Entry<String, BrokerData>> itBrokerAddrTable =this.brokerAddrTable.entrySet().iterator();while (itBrokerAddrTable.hasNext() && (null == brokerNameFound)) {BrokerData brokerData = itBrokerAddrTable.next().getValue();Iterator<Entry<Long, String>> it = brokerData.getBrokerAddrs().entrySet().iterator();while (it.hasNext()) {Entry<Long, String> entry = it.next();Long brokerId = entry.getKey();String brokerAddr = entry.getValue();if (brokerAddr.equals(brokerAddrFound)) {brokerNameFound = brokerData.getBrokerName();it.remove();log.info("remove brokerAddr[{}, {}] from brokerAddrTable, because channel destroyed",brokerId, brokerAddr);break;}}if (brokerData.getBrokerAddrs().isEmpty()) {removeBrokerName = true;itBrokerAddrTable.remove();log.info("remove brokerName[{}] from brokerAddrTable, because channel destroyed",brokerData.getBrokerName());}}// Step3:根据BrokerName从clusterAddrTable中找到Broker并将其从集群中移除// 如果移除后,集群中不包含任何Broker,就将该集群中clusterAddrTable中移除if (brokerNameFound != null && removeBrokerName) {// 遍历Broker集群Iterator<Entry<String, Set<String>>> it = this.clusterAddrTable.entrySet().iterator();while (it.hasNext()) {Entry<String, Set<String>> entry = it.next();String clusterName = entry.getKey();Set<String> brokerNames = entry.getValue();boolean removed = brokerNames.remove(brokerNameFound);if (removed) {log.info("remove brokerName[{}], clusterName[{}] from clusterAddrTable, because channel destroyed",brokerNameFound, clusterName);// 如果集群中已经不包含任何Broker,移除该集群if (brokerNames.isEmpty()) {log.info("remove the clusterName[{}] from clusterAddrTable, because channel destroyed and no broker in this cluster",clusterName);it.remove();}break;}}}// Step4:根据BrokerName,遍历所有主题队列,如果队列中包含当前Broker的队列,就移除// 如果topic中只包含待移除的Broker队列,就从路由表中删除该topicif (removeBrokerName) {// 遍历所有主题队列Iterator<Entry<String, List<QueueData>>> itTopicQueueTable =this.topicQueueTable.entrySet().iterator();while (itTopicQueueTable.hasNext()) {Entry<String, List<QueueData>> entry = itTopicQueueTable.next();String topic = entry.getKey();List<QueueData> queueDataList = entry.getValue();// 主题的所有队列信息Iterator<QueueData> itQueueData = queueDataList.iterator();while (itQueueData.hasNext()) {// 主题的某个队列信息QueueData queueData = itQueueData.next();// 队列所属Broker名称和被移除的Broker名称一样,就移除该队列if (queueData.getBrokerName().equals(brokerNameFound)) {itQueueData.remove();log.info("remove topic[{} {}], from topicQueueTable, because channel destroyed",topic, queueData);}}// 如果队列已经为空,就移除主题if (queueDataList.isEmpty()) {itTopicQueueTable.remove();log.info("remove topic[{}] all queue, from topicQueueTable, because channel destroyed",topic);}}}} finally {// Step5:释放锁this.lock.writeLock().unlock();}} catch (Exception e) {log.error("onChannelDestroy Exception", e);}}}
5. Broker 心跳注册(路由注册)
5.1. 时序图
5.2. 路由元数据信息
Broker 会把自己的信息注册到Nameserver中,注册的信息就叫路由元数据信息,具体注册信息如下:
// 1. Topic 消息队列的路由信息,消息发送时根据路由表进行负载均衡private final HashMap<String/* topic */, List<QueueData>> topicQueueTable;// 2. Broker 基础信息,包含brokerName、所属集群名称、主备Broker地址private final HashMap<String/* brokerName */, BrokerData> brokerAddrTable;// 3. Broker集群信息private final HashMap<String/* clusterName */, Set<String/* brokerName */>> clusterAddrTable;// 4. Broker 状态信息,NameServer 每次收到心跳包时会替换该信息private final HashMap<String/* brokerAddr */, BrokerLiveInfo> brokerLiveTable;// 5. Broker 上的FilterServer列表,用于类模式消息过滤private final HashMap<String/* brokerAddr */, List<String>/* Filter Server */> filterServerTable;
- topicQueueTable:topic 消息队列的路由信息,消息发送时根据路由表进行负债均衡
- brokerAddrTable:Broker基本信息,包含brokerName、所属集群名称、主备Broker地址
- clusterAddrTable:Broker集群信息,存储集群中所有Broker 的名称
- brokerLiveTable:Broker 状态信息,NameServer 每次收到心跳包(注册Broker信息)会替换该值
filterServerTable:Broker上的FilterServer列表,用于类模式消息过滤。
5.3. 源码分析
5.3.1. Broker 发送心跳包(注册)
Broker 会每隔10s向Nameserver 发送一次心跳,发送心跳的入口代码是:org.apache.rocketmq.broker#start()
Broker启动后会创建一个线程,在程序启动10秒后执行,每隔30秒(默认30s,时间间隔在10秒到60秒之间,BrokerConfig.getRegisterNameServerPeriod()的默认值是30秒)执行一次// 向NameServer 发送心跳包,每隔10s发送一次this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {try {BrokerController.this.registerBrokerAll(true, false, brokerConfig.isForceRegister());} catch (Throwable e) {log.error("registerBrokerAll Exception", e);}}}, 1000 * 10, Math.max(10000, Math.min(brokerConfig.getRegisterNameServerPeriod(), 60000)), TimeUnit.MILLISECONDS);
这里主要是组装一些心跳包检测的必要参数
public synchronized void registerBrokerAll(final boolean checkOrderConfig, boolean oneway, boolean forceRegister) {// 构建Topic信息TopicConfigSerializeWrapper topicConfigWrapper = this.getTopicConfigManager().buildTopicConfigSerializeWrapper();if (!PermName.isWriteable(this.getBrokerConfig().getBrokerPermission())|| !PermName.isReadable(this.getBrokerConfig().getBrokerPermission())) {ConcurrentHashMap<String, TopicConfig> topicConfigTable = new ConcurrentHashMap<String, TopicConfig>();for (TopicConfig topicConfig : topicConfigWrapper.getTopicConfigTable().values()) {TopicConfig tmp =new TopicConfig(topicConfig.getTopicName(), topicConfig.getReadQueueNums(), topicConfig.getWriteQueueNums(),this.brokerConfig.getBrokerPermission());topicConfigTable.put(topicConfig.getTopicName(), tmp);}topicConfigWrapper.setTopicConfigTable(topicConfigTable);}// 强制注册或者需要注册时,向NameServer 注册Broker信息if (forceRegister || needRegister(this.brokerConfig.getBrokerClusterName(),this.getBrokerAddr(),this.brokerConfig.getBrokerName(),this.brokerConfig.getBrokerId(),this.brokerConfig.getRegisterBrokerTimeoutMills())) {// 发送心跳包doRegisterBrokerAll(checkOrderConfig, oneway, topicConfigWrapper);}}
心跳包的发送
/*** 向NameServer 注册Broker* @param checkOrderConfig* @param oneway* @param topicConfigWrapper*/private void doRegisterBrokerAll(boolean checkOrderConfig, boolean oneway,TopicConfigSerializeWrapper topicConfigWrapper) {// 发送心跳包List<RegisterBrokerResult> registerBrokerResultList = this.brokerOuterAPI.registerBrokerAll(this.brokerConfig.getBrokerClusterName(),this.getBrokerAddr(),this.brokerConfig.getBrokerName(),this.brokerConfig.getBrokerId(),this.getHAServerAddr(),topicConfigWrapper,this.filterServerManager.buildNewFilterServerList(),oneway,this.brokerConfig.getRegisterBrokerTimeoutMills(),this.brokerConfig.isCompressedRegister());if (registerBrokerResultList.size() > 0) {RegisterBrokerResult registerBrokerResult = registerBrokerResultList.get(0);if (registerBrokerResult != null) {if (this.updateMasterHAServerAddrPeriodically && registerBrokerResult.getHaServerAddr() != null) {this.messageStore.updateHaMasterAddress(registerBrokerResult.getHaServerAddr());}this.slaveSynchronize.setMasterAddr(registerBrokerResult.getMasterAddr());if (checkOrderConfig) {this.getTopicConfigManager().updateOrderTopicConfig(registerBrokerResult.getKvTable());}}}}
Broker 心跳包的发送最终由org.apache.rocketmq.broker.out.BrokerOuterAPI#registerBrokerAll()方法实现发送,遍历所有Nameserver,依次向每台Nameserver发送心跳
发送心跳包的具体逻辑:brokerAddr: broker地址
- brokerId:brokerId=0表示主节点,brokerId>0表示从节点
- brokerName:broker 名称
- clusterName :集群名称
- haServerAddr:主节点地址,初次请求时该值为空,从节点向NameServer注册后返回
- requestBody 心跳包参数对象,包含topicConfigSerializeWrapper属性,这个属性内部封装的是TopicConfigManager中的topicConfigTable属性,是一个HashMap,topicConfigTable属性存储的是Broker启动时默认的一些topic
filterServerList:消息过滤服务器列表
/*** 发送心跳包* @param clusterName 集群名称* @param brokerAddr broker地址* @param brokerName broker名称* @param brokerId brokerId=0表示主节点,brokerId>0表示从节点* @param haServerAddr 主节点地址,初次请求时该值为空,从节点向NameServer注册后返回* @param topicConfigWrapper 主题配置,内部封装的是TopicConfigManager中的topicConfigTable,内部存储的是Broker启动时默认的一些topic* 如:MinA11.SELF_TEST_TOPIC、MixA11.DEFAULT_TOPIC(AutoCreateTopicEnable=true* @param filterServerList 消息过滤服务列表* @param oneway* @param timeoutMills* @param compressed* @return*/public List<RegisterBrokerResult> registerBrokerAll(final String clusterName,final String brokerAddr,final String brokerName,final long brokerId,final String haServerAddr,final TopicConfigSerializeWrapper topicConfigWrapper,final List<String> filterServerList,final boolean oneway,final int timeoutMills,final boolean compressed) {// 向NameServer发送心跳包的结果结合final List<RegisterBrokerResult> registerBrokerResultList = new CopyOnWriteArrayList<>();// 获取所有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);// 向NameServer 发送心跳包的参数RegisterBrokerBody requestBody = new RegisterBrokerBody();//主题配置,内部封装的是TopicConfigManager中的topicConfigTable,内部存储的是Broker启动时默认的一些topic// 如:MinA11.SELF_TEST_TOPIC、MixA11.DEFAULT_TOPIC(AutoCreateTopicEnable=truerequestBody.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,依次向每个NameServer发送心跳包for (final String namesrvAddr : nameServerAddressList) {brokerOuterExecutor.execute(new Runnable() {@Overridepublic void run() {try {// 向NameServer发送心跳,返回心跳发送结果RegisterBrokerResult result = registerBroker(namesrvAddr, oneway, timeoutMills, requestHeader, body);// 将发送结果放到registerBrokerResultList 集合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();}}});}try {// 等待Broker 注冊,如果注册超时抛异常countDownLatch.await(timeoutMills, TimeUnit.MILLISECONDS);} catch (InterruptedException e) {}}return registerBrokerResultList;}
路由注册最终通过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 {// 注册Broker 的Netty请求对象,Broker 注册的requestCode=RequestCode.REGISTER_BROKER// Nameserver 端的接收请求后,会根据requestCode 转发给不同的业务处理RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.REGISTER_BROKER, requestHeader);request.setBody(body);if (oneway) {try {// 单向调用,没有返回值,不返回nameserver 返回结果this.remotingClient.invokeOneway(namesrvAddr, request, timeoutMills);} catch (RemotingTooMuchRequestException e) {// Ignore}return null;}// 异步调用向nameserver注册Broker,会有返回值RemotingCommand response = this.remotingClient.invokeSync(namesrvAddr, request, timeoutMills);assert response != null;switch (response.getCode()) {case ResponseCode.SUCCESS: {// 注册成功后,获取返回的responseHeaderRegisterBrokerResponseHeader responseHeader =(RegisterBrokerResponseHeader) response.decodeCommandCustomHeader(RegisterBrokerResponseHeader.class);// 封装返回结果,更新masterAddr和haServerAddrRegisterBrokerResult result = new RegisterBrokerResult();result.setMasterAddr(responseHeader.getMasterAddr());result.setHaServerAddr(responseHeader.getHaServerAddr());if (response.getBody() != null) {result.setKvTable(KVTable.decode(response.getBody(), KVTable.class));}return result;}default:break;}throw new MQBrokerException(response.getCode(), response.getRemark(), requestHeader == null ? null : requestHeader.getBrokerAddr());}
5.3.2. NameServer 处理心跳包(注册)
在Broker 发送心跳包后,Nameserver负责接收,并处理心跳。Broker发出路由注册的心跳包之后,NameServer会根据心跳包中的requestCode进行处理。NameServer的默认网络处理器是org.apache.rocketmq.namesrv.processor.DefaultRequestProcessor类,Broker向NameServer通过Netty发送心跳时,会发送requestCode=RequestCode._REGISTER_BROKER,接着会将心跳注册请求转发给_registerBroker()方法处理
/*** Netty 接收请求处理* @param ctx* @param request* @return* @throws RemotingCommandException*/@Overridepublic RemotingCommand processRequest(ChannelHandlerContext ctx,// ...省略代码switch (request.getCode()) {// ...省略代码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 {// 向NameServer 注册Brokerreturn this.registerBroker(ctx, request);}//... 省略代码}return null;}
Nameserver 接收Broker心跳包最终由org.apache.rocketmq.namesrv.routeinfo.registerBroker()处理
/*** 向NameServer注册Broker 信息* @param clusterName Broker 集群名称* @param brokerAddr Broker 地址* @param brokerName Broker 名称* @param brokerId BrokerId 大于0为从节点,0表示主节点* @param haServerAddr 主节点地址,初次请求时为空,从节点向Nameserver注册后返回* @param topicConfigWrapper Topic 配置信息* @param filterServerList 消息过滤服务器列表* @param channel Netty 通道对象,知道是哪个Netty客户端* @return*/public RegisterBrokerResult registerBroker(final String clusterName,final String brokerAddr,final String brokerName,final long brokerId,final String haServerAddr,final TopicConfigSerializeWrapper topicConfigWrapper,final List<String> filterServerList,final Channel channel) {RegisterBrokerResult result = new RegisterBrokerResult();try {try {// Step1:路由注册加写锁,防止并发修改RouteInfoManager中路由表this.lock.writeLock().lockInterruptibly();// 判断Broker 所属集群是否存在,不存在则创建集群,然后将Broker名加入集群Broker集合Set<String> brokerNames = this.clusterAddrTable.get(clusterName);if (null == brokerNames) {brokerNames = new HashSet<String>();// 将Broker 加入 clusterAddrTable 集群集合this.clusterAddrTable.put(clusterName, brokerNames);}brokerNames.add(brokerName);// 是否第一次注册Broker信息,默认不是第一次boolean registerFirst = false;// Step2:维护BrokerData信息,尝试从brokerAddrTable 获取Broker信息// 首先从brokerAddrTable中根据broker名称获取Broker信息,如果不存在就新建BrokerData并放入brokerAddrTableBrokerData brokerData = this.brokerAddrTable.get(brokerName);// Broker信息不存在if (null == brokerData) {// 设置Broker信息已经被注册,true表示第一次注册registerFirst = true;// 创建BrokerData信息brokerData = new BrokerData(clusterName, brokerName, new HashMap<Long, String>());// BrokerData信息加入brokerAddrTablethis.brokerAddrTable.put(brokerName, brokerData);}Map<Long, String> brokerAddrsMap = brokerData.getBrokerAddrs();//Switch slave to master: first remove <1, IP:PORT> in namesrv, then add <0, IP:PORT>//The same IP:PORT must only have one record in brokerAddrTableIterator<Entry<Long, String>> it = brokerAddrsMap.entrySet().iterator();while (it.hasNext()) {Entry<Long, String> item = it.next();if (null != brokerAddr && brokerAddr.equals(item.getValue()) && brokerId != item.getKey()) {it.remove();}}String oldAddr = brokerData.getBrokerAddrs().put(brokerId, brokerAddr);registerFirst = registerFirst || (null == oldAddr);// Step3:如果Broker 为主节点,并且Broker的topic配置信息发生变化或者是初次注册// 需要创建或者更新topic路由元数据,填充topicQueueTabe,如果是默认主题自动注册路由信息,其中包含// MixAll.DEFAULTOPIC的路由信息。当消息生产者发送主题时,如果该主题没有创建,并且BrokerConfig的autoCreateTopicEnable=true// 那么就返回MixAll_DEFAULT_TOPIC的路由信息if (null != topicConfigWrapper&& MixAll.MASTER_ID == brokerId) {// Broker的topic配置信息发生变化或者初次注册Broker// 创建或者更新topic路由元信息// 校验版本,决定是否需要创建或者更新topic路由元信息if (this.isBrokerTopicConfigChanged(brokerAddr, topicConfigWrapper.getDataVersion())|| registerFirst) {ConcurrentMap<String, TopicConfig> tcTable =topicConfigWrapper.getTopicConfigTable();if (tcTable != null) {// 创建或者更新topic 路由元信息for (Map.Entry<String, TopicConfig> entry : tcTable.entrySet()) {// 根据topicConfig创建QueueData数据结构,然后更新topicQueueTablethis.createAndUpdateQueueData(brokerName, entry.getValue());}}}}// 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的主节点信息,并更新对应的masterAddr属性if (filterServerList != null) {if (filterServerList.isEmpty()) {this.filterServerTable.remove(brokerAddr);} else {this.filterServerTable.put(brokerAddr, filterServerList);}}// Broker 是从节点if (MixAll.MASTER_ID != brokerId) {// Broker主节点地址String masterAddr = brokerData.getBrokerAddrs().get(MixAll.MASTER_ID);if (masterAddr != null) {BrokerLiveInfo brokerLiveInfo = this.brokerLiveTable.get(masterAddr);// 更新masterAddrif (brokerLiveInfo != null) {result.setHaServerAddr(brokerLiveInfo.getHaServerAddr());result.setMasterAddr(masterAddr);}}}} finally {this.lock.writeLock().unlock();}} catch (Exception e) {log.error("registerBroker Exception", e);}return result;}
