RocketMQ源码解析之MQ服务器架构设计

479 阅读3分钟

1:架构图

1.1:NameServer

在RocketMQ中,提供路由注册,发现,剔除等功能,并没有使用zk,redis等作为注册中心,而是使用NameServer作为注册中心,根据官方的话来说是简单,容易实现,并且在RocketMQ中NameServer之间是不存在通信的,也就是所谓的集群不通信,这点或许就是RocketMQ的精髓之一吧,将一些复杂的业务交给别人去实现,RocketMQ始终遵循着可以,但是没有必要的原则(最好的实现方式就是不实现 -- 可以实现,但是没有必要实现)

1.2:Broker

作为RocketMQ最核心的组件之一,掌管着消息配置,消息队列管理,存储等重要的功能,可以说是整个MQ的核心。

1.3:Producer

生产者,用于生产消息,必然会需要一些消息队列相关的配置信息,从而直接先从NameServer上面拉取,根据返回的信息找到对应的Broker,从而实现消息的精准发送到消息队列,这一点和dubbo中很类似,在使用注册中心为zk的情况下,客户端要想调用一个api的时候,会从注册中心拉取到对应api的地址,然后再进行远程调用是一个道理的。

img

2:源码解析

2.1:NameServer定时扫描,剔除

//定时任务扫描活跃的Broker,每10s一次
//如果刚刚好扫描完,然后Broker下线,此时会有一段事件Broker是不可用的,是怎么处理的,请看下回分解
this.scheduledExecutorService.scheduleAtFixedRate(() -> NamesrvController.this.routeInfoManager.scanNotActiveBroker(), 5, 10, TimeUnit.SECONDS);



public void scanNotActiveBroker() {
        Iterator<Entry<String, BrokerLiveInfo>> it = this.brokerLiveTable.entrySet().iterator();
//获取存活的Broker
        while (it.hasNext()) {
            Entry<String, BrokerLiveInfo> next = it.next();
//获取上一次Broker的时间戳,用来对比是否超过 120s
            long last = next.getValue().getLastUpdateTimestamp();
//private final static long BROKER_CHANNEL_EXPIRED_TIME = 1000 * 60 * 2; 
            if ((last + BROKER_CHANNEL_EXPIRED_TIME) < System.currentTimeMillis()) {
//如果超过了120s,则剔除这个已经过期的Broker
                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());
            }
        }
    }

2.2:Broker发送心跳包

// 定时向NameServer发动心跳包,报告自己还存活着
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);
                }
            }
// org.apache.rocketmq.common.BrokerConfig#registerNameServerPeriod
// registerNameServerPeriod = 1000 * 30;
        }, 1000 * 10, Math.max(10000, Math.min(brokerConfig.getRegisterNameServerPeriod(), 60000)), TimeUnit.MILLISECONDS);

使用BrokerAPI真正发送心跳包 - 包装请求信息- 使用netty通信

org.apache.rocketmq.broker.BrokerController#registerBrokerAll

org.apache.rocketmq.broker.BrokerController#doRegisterBrokerAll

​ org.apache.rocketmq.broker.out.BrokerOuterAPI#registerBrokerAll

​ org.apache.rocketmq.remoting.netty.NettyRemotingClient#invokeOneway

 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) {

        final List<RegisterBrokerResult> registerBrokerResultList = Lists.newArrayList();
        List<String> nameServerAddressList = this.remotingClient.getNameServerAddressList();
        //获取nameserver的列表
        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());
            //循环列表
            for (final String namesrvAddr : nameServerAddressList) {
                brokerOuterExecutor.execute(() -> {
                    try {
                        //包装请求头信息 
                        /**
                         * brokerAddr
                         * brokerId
                         * brokerName
                         * clusterName
                         * haServerAddr
                         * compressed
                         */
                        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();
                    }
                });
            }

2.3:使用netty发送数据

 @Override
    public void invokeOneway(String addr, RemotingCommand request, long timeoutMillis) throws InterruptedException,
        RemotingConnectException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException {
        final Channel channel = this.getAndCreateChannel(addr);
        if (channel != null && channel.isActive()) {
            try {
                doBeforeRpcHooks(addr, request);
                this.invokeOnewayImpl(channel, request, timeoutMillis);
            } catch (RemotingSendRequestException e) {
                log.warn("invokeOneway: send request exception, so close the channel[{}]", addr);
                this.closeChannel(addr, channel);
                throw e;
            }
        } else {
            this.closeChannel(addr, channel);
            throw new RemotingConnectException(addr);
        }
    }

NameServer 处理逻辑

使用到了 NameServer的实现类RouteInfoManager

org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#registerBroker

#获取集群下面的Broker(在发送心跳包的时候,已经将clusterName放人到其中)

 //根据集群名称获取当前集群下所有的Broker
                Set<String> brokerNames = this.clusterAddrTable.get(clusterName);
                if (null == brokerNames) {
                    brokerNames = new HashSet<String>();
                    this.clusterAddrTable.put(clusterName, brokerNames);
                }

#当前Broker注册表获取不到broker,那么新建一个,并添加到Broker中去

/获取不到broker新来的,需要新增一个
                BrokerData brokerData = this.brokerAddrTable.get(brokerName);
                if (null == brokerData) {
                    registerFirst = true;
                    brokerData = new BrokerData(clusterName, brokerName, new HashMap<Long, String>());
                    this.brokerAddrTable.put(brokerName, brokerData);
                }

#添加Broker注册表, 并进行更新Broker的最新的心跳时间戳

 //添加注册表
                String oldAddr = brokerData.getBrokerAddrs().put(brokerId, brokerAddr);
                registerFirst = registerFirst || (null == oldAddr);

                if (null != topicConfigWrapper
                    && MixAll.MASTER_ID == brokerId) {
                    if (this.isBrokerTopicConfigChanged(brokerAddr, topicConfigWrapper.getDataVersion())
                        || registerFirst) {
                        ConcurrentMap<String, TopicConfig> tcTable =
                            topicConfigWrapper.getTopicConfigTable();
                        if (tcTable != null) {
                            //f更新注册Broker的注册时间戳,便于NameServer检测Broker下线
                            for (Map.Entry<String, TopicConfig> entry : tcTable.entrySet()) {
                                this.createAndUpdateQueueData(brokerName, entry.getValue());
                            }
                        }
                    }
                }

2.3:路由发现

public RemotingCommand getRouteInfoByTopic(ChannelHandlerContext ctx,
        RemotingCommand request) throws RemotingCommandException {
        final RemotingCommand response = RemotingCommand.createResponseCommand(null);
        final GetRouteInfoRequestHeader requestHeader =
            (GetRouteInfoRequestHeader) request.decodeCommandCustomHeader(GetRouteInfoRequestHeader.class);
        
        //1.通过nameServer获取路由信息
        TopicRouteData topicRouteData = this.namesrvController.getRouteInfoManager().pickupTopicRouteData(requestHeader.getTopic());

        if (topicRouteData != null) {
            //2.获取是否为顺序消息配置,如果是顺序消息,需要填充  topic头为 NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG,声明为顺序消息
            if (this.namesrvController.getNamesrvConfig().isOrderMessageEnable()) {
                //3. 如果是顺序消息,需要填充  topic头为 NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG,声明为顺序消息
                String orderTopicConf =
                    this.namesrvController.getKvConfigManager().getKVConfig(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG,
                        requestHeader.getTopic());
                topicRouteData.setOrderTopicConf(orderTopicConf);
            }

            byte[] content = topicRouteData.encode();
            response.setBody(content);
            response.setCode(ResponseCode.SUCCESS);
            response.setRemark(null);
            return response;
        }

获取路由信息

#org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#pickupTopicRouteData

 try {
                this.lock.readLock().lockInterruptibly();
                //1.通过topic获取到队列信息,里面是携带brokerName的
                //org.apache.rocketmq.common.protocol.route.QueueData
                /**
                 private String brokerName;
                 private int readQueueNums;
                 private int writeQueueNums;
                 private int perm;
                 private int topicSynFlag;
                 */
                List<QueueData> queueDataList = this.topicQueueTable.get(topic);
                if (queueDataList != null) {
                    topicRouteData.setQueueDatas(queueDataList);
                    foundQueueData = true;

                    Iterator<QueueData> it = queueDataList.iterator();
                    while (it.hasNext()) {
                        QueueData qd = it.next();
                        //2.获取BrokerName 的集合
                        brokerNameSet.add(qd.getBrokerName());
                    }

                    for (String brokerName : brokerNameSet) {
                        //3. 通过brokerName 获取到Broker
                        BrokerData brokerData = this.brokerAddrTable.get(brokerName);
                        if (null != brokerData) {
                            BrokerData brokerDataClone = new BrokerData(brokerData.getCluster(), brokerData.getBrokerName(), (HashMap<Long, String>) brokerData
                                .getBrokerAddrs().clone());
                            brokerDataList.add(brokerDataClone);
                            foundBrokerData = true;
                            for (final String brokerAddr : brokerDataClone.getBrokerAddrs().values()) {
                                List<String> filterServerList = this.filterServerTable.get(brokerAddr);
                                filterServerMap.put(brokerAddr, filterServerList);
                            }
                        }
                    }
                }