MetaQ技术内幕——源码分析(五)
Broker接收从Producer(Client端)发送的消息,也能够返回消息到Consumer(Client),对于Broker来说,就是网络输入输出流的处理。
Broker使用淘宝内部的gecko框架作为网络传输框架,gecko是一个NIO框架,能够支持一下特性:
1、 可自定义协议,协议可扩展、紧凑、高效
2、 可自动管理重连,重连由客户端发起
3、 需进行心跳检测,及时发现连接失效
4、 请求应答模型应当支持同步和异步
5、 连接的分组管理,并且在重连情况下能正确处理连接的分组
6、 请求的发送应当支持四种模型: (1) 向单个连接发起请求 (2) 向分组内的某个连接发起请求,这个选择策略可定义 (3) 向分组内的所有连接发起请求 (4) 向多个分组发起请求,每个分组的请求遵循(2)
7、 编程模型上尽量做到简单、易用、透明,向上层代码屏蔽网络处理的复杂细节。
8、 高度可配置,包括网络参数、服务层参数等
9、 高度可靠,正确处理网络异常,对内存泄露等隐患有预防措施
10、 可扩展
如果时间允许的话,笔者也可以做一下 gecko的源码分析
由于网络模块与其他模块关联性极强,不像存储模块可以独立分析,所以此篇文章开始将从全局开始分析Broker。
先看看Broker的启动类MetamorphosisStartup:
public static void main(final String[] args) { final String configFilePath = getConfigFilePath(args); final MetaConfig metaConfig = getMetaConfig(configFilePath); final MetaMorphosisBroker server = new MetaMorphosisBroker(metaConfig); server.start(); }
从MetamorphosisStartup可以看出其逻辑是先加载了配置文件,然后构造了MetaMorphosisBroker实例,并调用该实例的start方法,MetaMorphosisBroker才是Broker真正的启动类。
看看真正的启动类MetaMorphosisBroker, MetaMorphosisBroker实现接口MetaMorphosisBrokerMBean,可以通过 jmx 协议关闭MetaMorphosisBroker。看看在构造MetaMorphosisBroker实例的时候干了些什么事情。
public MetaMorphosisBroker(final MetaConfig metaConfig) { //配置信息 this.metaConfig = metaConfig; //Broker对外提供的nio Server this.remotingServer = newRemotingServer(metaConfig); //线程池管理器,主要是提供给nio Server在并发环境下可以使用多线程处理,提高性能 this.executorsManager = new ExecutorsManager(metaConfig); //全局唯一的id生成器 this.idWorker = new IdWorker(metaConfig.getBrokerId()); //存储模块管理器 this.storeManager = new MessageStoreManager(metaConfig, this.newDeletePolicy(metaConfig)); //统计模块管理器 this.statsManager = new StatsManager(this.metaConfig, this.storeManager, this.remotingServer); //zookeeper客户端,前面介绍过metaq使用zookeeper作为中间协调者,Broker会将自己注册到zookeeper上,也会从zookeeper查询相关数据 this.brokerZooKeeper = new BrokerZooKeeper(metaConfig); //网络输入输出流处理器 final BrokerCommandProcessor next = new BrokerCommandProcessor(this.storeManager, this.executorsManager, this.statsManager, this.remotingServer, metaConfig, this.idWorker, this.brokerZooKeeper); //事务存储引擎 JournalTransactionStore transactionStore = null; try { transactionStore = new JournalTransactionStore(metaConfig.getDataLogPath(), this.storeManager, metaConfig); } catch (final Exception e) { throw new MetamorphosisServerStartupException("Initializing transaction store failed.", e); } //带事务处理的网络输入输出流处理器,设计采用了责任链的设计模式,使用事务存储引擎存储中间结果 this.brokerProcessor = new TransactionalCommandProcessor(metaConfig, this.storeManager, this.idWorker, next, transactionStore, this.statsManager); //钩子,JVM退出钩子,钩子实现在JVM退出的时候尽力正确关闭 MetaMorphosisBroker this.shutdownHook = new ShutdownHook(); //注册钩子 Runtime.getRuntime().addShutdownHook(this.shutdownHook); //注册MBean,因为MetaMorphosisBroker实现MetaMorphosisBrokerMBean接口,可以将自己作为MBean注册到MBeanServer MetaMBeanServer.registMBean(this, null); }
前面我们知道在启动的时候会调用MetaMorphosisBroker的start() 方法,来看看start()方法里究竟做了些什么事情
public synchronized void start() { //判断是否已经启动,如果已经启动,则不在启动 if (!this.shutdown) { return; } this.shutdown = false; //初始化存储模块,加载验证已有数据 this.storeManager.init(); //初始化线程池 this.executorsManager.init(); //初始化统计模块 this.statsManager.init(); //向nio server注册处理器 this.registerProcessors(); try { //NIO server启动 this.remotingServer.start(); } catch (final NotifyRemotingException e) { throw new MetamorphosisServerStartupException("start remoting server failed", e); } try { //在/brokers/ids下创建临时节点,名称为节点Id this.brokerZooKeeper.registerBrokerInZk(); //如果为master节点,则创建/brokers/ids/master_config_checksum节点 this.brokerZooKeeper.registerMasterConfigFileChecksumInZk(); //添加主题列表监听器,监听主题列表变化,如果主题列表发生变化,则向zookeeper重新注册主题和分区信息 this.addTopicsChangeListener(); //注册主题和分区信息 this.registerTopicsInZk(); //设置标志位主题和分区注册成功 this.registerZkSuccess = true; } catch (final Exception e) { this.registerZkSuccess = false; throw new MetamorphosisServerStartupException("Register broker to zk failed", e); } log.info("Starting metamorphosis server..."); //初始化输入输出流处理器 this.brokerProcessor.init(); log.info("Start metamorphosis server successfully"); }
下面,让我们具体来看看start()方法里调用的MetaMorphosisBroker每一个方法,首先是registerProcessors()方法:
private void registerProcessors() { //注册Get命令处理器 this.remotingServer.registerProcessor(GetCommand.class, new GetProcessor(this.brokerProcessor, this.executorsManager.getGetExecutor())); //注册Put命令的处理器 this.remotingServer.registerProcessor(PutCommand.class, new PutProcessor(this.brokerProcessor, this.executorsManager.getUnOrderedPutExecutor())); //查询最近有效的offset处理器 this.remotingServer.registerProcessor(OffsetCommand.class, new OffsetProcessor(this.brokerProcessor, this.executorsManager.getGetExecutor())); //心跳检测处理器 this.remotingServer.registerProcessor(HeartBeatRequestCommand.class, new VersionProcessor(this.brokerProcessor)); //注册退出命令处理器 this.remotingServer.registerProcessor(QuitCommand.class, new QuitProcessor(this.brokerProcessor)); //注册统计信息查询处理器 this.remotingServer.registerProcessor(StatsCommand.class, new StatsProcessor(this.brokerProcessor)); //注册事务命令处理器 this.remotingServer.registerProcessor(TransactionCommand.class, new TransactionProcessor(this.brokerProcessor, this.executorsManager.getUnOrderedPutExecutor())); }
依赖于不同的处理器,可以将不同的请求进行处理并返回结果。接下来就是addTopicsChangeListener()方法。
//addTopicsChangeListener方法比较简单,主要简单配置的topic列表的变化,前面介绍过MetaConfig提供监听机制监听topic列表的变化,该方法向MetaConfig注册一个匿名监听器监听topic列表变化,一旦发生变化则向zookeeper进行注册 private void addTopicsChangeListener() { // 监听topics列表变化并注册到zk this.metaConfig.addPropertyChangeListener("topics", new PropertyChangeListener() { public void propertyChange(final PropertyChangeEvent evt) { try { MetaMorphosisBroker.this.registerTopicsInZk(); } catch (final Exception e) { log.error("Register topic in zk failed", e); } } }); }
MetaMorphosisBroker在启动过程中被调用的方法还有registerTopicsInZk()方法,registerTopicsInZk完成向zookeeper注册topic和分区信息功能。在分析方法之前,有必要插入分析一下Broker在zk上注册的结构,代码在common工程的类MetaZookeeper,该结构是Broker和Client共享的。
Zk中有4中类型的根目录,分别是:
1) /consumers:存放消费者列表以及消费记录,消费者列表主要是以组的方式存在,结构主要如下:
/consumers/xxGroup/ids/xxConsumerId:DATA (“:”后的DATA表示节点xxConsumerId对应的数据) 组内消费者Id;DATA为订阅主题列表,以”,”分隔
/consumers/xxGroup/offsets/xxTopic/分区N:DATA 组内主题分区N的消费进度;DATA为topic下分区N具体进度值
/consumers/xxGroup/owners/xxTopic/分区N:DATA 组内主题分区N的的消费者 ;DATA为消费者ID,表示XXTopic下分区N的数据由指定的消费者进行消费
2) /brokers/ids:存放Broker列表,如果Broker与Zookeeper失去连接,则会自动注销在/brokers/ids下的broker记录,例子如下:
/brokers/ids/xxBroker
3) /brokers/topics-pub:存放发布的主题列表以及对应的可发送消息的Broker列表,例子如下:
/brokers/topics-pub/xxTopic/xxBroker
/brokers/topics-pub下记录的是可发送消息到xxTopic的Broker列表,意味着有多少个Broker允许存储Client发送到Topic数据
4) /brokers/topics-sub:存放订阅的主题列表以及对应可订阅的Broker列表,例子如下:
/brokers/topics-sub/xxTopic/xxBroker
/brokers/topics-sub下记录的可订阅xxTopic的Broker列表,意味着有多少个Broker允许被Client订阅topic的数据
具体代码如下:
public MetaZookeeper(final ZkClient zkClient, final String root) { //zk客户端 this.zkClient = zkClient; //根路径,默认为空 this.metaRoot = this.normalize(root); //前面讲的消费者列表 this.consumersPath = this.metaRoot + "/consumers"; //前面讲的brokers列表 this.brokerIdsPath = this.metaRoot + "/brokers/ids"; //前面讲的/brokers/topics-pub this.brokerTopicsPubPath = this.metaRoot + "/brokers/topics-pub"; //前面讲的/brokers/topics-sub this.brokerTopicsSubPath = this.metaRoot + "/brokers/topics-sub"; }
至于更复杂的,我们将在后面具体再进行分析,主要先了解该存储结构即可。
回归正题 , registerTopicsInZk 方法完成向 zookeeper 注册 topic 和分区信息功能
private void registerTopicsInZk() throws Exception { // 先注册配置的topic到zookeeper for (final String topic : this.metaConfig.getTopics()) { this.brokerZooKeeper.registerTopicInZk(topic, true); } // 注册加载的topic到zookeeper // 从下面代码可以看出,如果当前没有配置的topic,但前面配置过的topic如果有消息存在,依然会向zk注册,在某种程度,我认为这个设计不好,为什么? 答:我们前面分析过MessageStoreManager类,里面有getMessageStore()方法和getOrCreateMessageStore()方法,在调用getMessageStore()方法时没有检查参数topic是否在topicsPatSet列表中(topicsPatSet只包含了配置的topic),而getOrCreateMessageStore()方法却检查了,这就意味着使用getOrCreateMessageStore()方法时,如果要查询获取不在topicsPatSet列表中的MessageStore实例会抛出异常,而调用getMessageStore()不会,让人产生疑惑。个人见解认为一旦配置发生更改,如果要做热加载的话则先卸载再重新加载会更合适,而且在getOrCreateMessageStore()和getMessageStore()方法都使用topicsPatSet进行判断,保持一致性 for (final String topic : this.storeManager.getMessageStores().keySet()) { this.brokerZooKeeper.registerTopicInZk(topic, true); } }
MetaMorphosisBroker 还有两个方法,一个是 newDeletePolicy() 方法,另一个是stop() 方法。 newDeletePolicy() 用于生产全局的存储模块的删除策略,如果没有配置删除策略,则使用该策略。
//全局删除策略 private DeletePolicy newDeletePolicy(final MetaConfig metaConfig) { final String deletePolicy = metaConfig.getDeletePolicy(); if (deletePolicy != null) { return DeletePolicyFactory.getDeletePolicy(deletePolicy); } return null; }
而 stop() 方法则主要在 MetaMorphosisBroker 关闭的时候销毁资源,尽力保证MetaQ 的正确关闭。
public synchronized void stop() { //如果关闭了,则不再关闭 if (this.shutdown) { return; } log.info("Stopping metamorphosis server..."); this.shutdown = true; //关闭与zk连接,注销与当前节点相关的配置 this.brokerZooKeeper.close(this.registerZkSuccess); try { // Waiting for zookeeper to notify clients. Thread.sleep(this.brokerZooKeeper.getZkConfig().zkSyncTimeMs); } catch (InterruptedException e) { // ignore } //释放线程池 this.executorsManager.dispose(); //释放存储模块 this.storeManager.dispose(); //释放统计模块 this.statsManager.dispose(); //关闭NIO Server try { this.remotingServer.stop(); } catch (final NotifyRemotingException e) { log.error("Shutdown remoting server failed", e); } //释放输入输出流处理器 this.brokerProcessor.dispose(); //如果是独立的zk,则关闭zk EmbedZookeeperServer.getInstance().stop(); //释放钩子 if (!this.runShutdownHook && this.shutdownHook != null) { Runtime.getRuntime().removeShutdownHook(this.shutdownHook); } log.info("Stop metamorphosis server successfully"); }
今天, Broker 的分析先到这,前面介绍的 zk 中注册的结构是与 Client 相关的,这里也向大家介绍了一下,以后分析 Client 的时候,该结构将不再介绍了。
上一篇: MetaQ技术内幕——源码分析(六)
下一篇: MetaQ技术内幕——源码分析(四)
推荐阅读
-
并发编程(五)——AbstractQueuedSynchronizer 之 ReentrantLock源码分析
-
[五]类加载机制双亲委派机制 底层代码实现原理 源码分析 java类加载双亲委派机制是如何实现的
-
Javac编译原理 《深入分析java web 技术内幕》第四章
-
element-ui Tag、Dialog组件源码分析整理笔记(五)
-
PHP模糊查询技术实例分析【附源码下载】
-
Netty源码分析 (五)----- 数据如何在 pipeline 中流动
-
《RocketMQ技术内幕:RocketMQ架构设计与实现原理》—1.1.2 Eclipse调试RocketMQ源码...
-
Android基础之Handler机制(五)Handler源码分析
-
《深入分析Java Web技术内幕》读书笔记
-
6.Spark streaming技术内幕 : Job动态生成原理与源码解析