RocketM源码解析系列(一)从路由中心谈起

本系列参考书籍:《RocketMQ技术内幕》下载源码:https://github.com/apache/rocketmq.git

一、从路由中心谈起

首先来张图,看看RocketMQ到底是在干嘛。

 

4943997-91f8772691e001ec-2.png

说白了,就是接收生产者的消息,然后传递给消费者。虽然以前没有用过metaQ,但是这样一看又感觉到万物一理了,有空考虑将以前用到开源ROS的消息模式进行分析比较,看看有啥个区别和优劣点。

从上图我们也能发现一个问题,就是rocketMQ是基于Group机制的发布订阅模式,所以metaQ本质上就支持消息负载均衡。比如某个Topic有9条消息,其中一个Consumer Group有3个实例(3个进程 OR 3台机器),那么每个实例将均摊消费3条消息。

再来看个rocketMQ的物理部署图:

8668330-303c9f77c70f16c1.png

从图中我们可以看到,相比较于kafka的zookeeper大脑,rocketMQ的大脑是NameServer,Broker消息服务器(消息存储)、producter集群、consumer集群需要在NameServer进行注册,然后才能进行通信。

NameServer的启动流程看下org.apache.rocketmq.namesrv.NamesrvStartup,这个地方感觉没啥,就是创建了业务参数和网络参数。需要注意的是,在这里有个所谓的心跳检测,其实就是看看Broker的状态有没有挂掉,挂掉了就执行相应的逻辑,这个在下面会进行源码上的分析。

NameServer路由注册和剔除

前面说了,NameServer在监听着Broker的状态,那么先来看看Broker的哪些数据需要给它,路由注册和剔除就是围绕着这些数据进行的,路由注册和删除就是注册和删除这个数据,当然,这一大堆数据有个名词,叫做路由元消息。(以下的源码来自于org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager)

{
    // 消息队列路由消息
    private final HashMap<String/* topic */, List<QueueData>> topicQueueTable;
    // Broker基础消息
    private final HashMap<String/* brokerName */, BrokerData> brokerAddrTable;
    // Broker集群消息
    private final HashMap<String/* clusterName */, Set<String/* brokerName */>> clusterAddrTable;
    // Broker状态消息
    private final HashMap<String/* brokerAddr */, BrokerLiveInfo> brokerLiveTable;
    // Broker上的filterServer列表,意思就是Broker把消息过滤后才发送给消费者
    private final HashMap<String/* brokerAddr */, List<String>/* Filter Server */> filterServerTable;
}

理一下逻辑,NameServer集群中有很多NameServer,NameServer集群管着Broker集群,其中的Broker是Master-Slave架构,每个Broker中存着Topic,一个Topic又有着多个消息队列。

路由注册

意思就是我要把Broker注册到NameServer上面去,首先Broker启动就向NameServer发送心跳语句,每隔30s还向NameServer发送心跳包,然后NameServer就根据收到的心跳包更新它存储的路由元消息。

发送心跳包的代码就不展开了,就是设置了一个定时任务,向NameServer集群中的每个NameServer发送心跳包,网络传输基于Netty,这里需要看一下心跳包里具体的东西,后面才知道我们要处理哪些东西。

{
    //  broker地址
    requestHeader.setBrokerAddr(brokerAddr);
    //  判断是master还是slave;0代表master
    requestHeader.setBrokerId(brokerId);
    // broker名称
    requestHeader.setBrokerName(brokerName);
    // 集群名称
    requestHeader.setClusterName(clusterName);
    // master地址
    requestHeader.setHaServerAddr(haServerAddr);
    // 
    requestHeader.setCompressed(compressed);
    // 请求体
    RegisterBrokerBody requestBody = new RegisterBrokerBody();
    RegisterBrokerBody requestBody = new RegisterBrokerBody();
    // 主题配置,包含的信息;topicConfigTable
    requestBody.setTopicConfigSerializeWrapper(topicConfigWrapper);
    requestBody.setFilterServerList(filterServerList);
}

NameServer处理心跳包,我们看下源码:

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 {
                // 加写锁
                this.lock.writeLock().lockInterruptibly();

                // 1.根据集群名称判断判断当下是不是已经存在了broker集群。如没有,创建broker集群
                Set<String> brokerNames = this.clusterAddrTable.get(clusterName);
                if (null == brokerNames) {
                    brokerNames = new HashSet<String>();
                    this.clusterAddrTable.put(clusterName, brokerNames);
                }
                brokerNames.add(brokerName);

                // 是不是第一次注册
                boolean registerFirst = false;

                // 2.获取broker基础信息
                BrokerData brokerData = this.brokerAddrTable.get(brokerName);
                // 2.1如果为空的话进行组装并加入到brokerAddrTable中
                if (null == brokerData) {
                    registerFirst = true;
                    brokerData = new BrokerData(clusterName, brokerName, new HashMap<Long, String>());
                    this.brokerAddrTable.put(brokerName, brokerData);
                }
                /* BrokerData所包含的属性值
                * private String cluster;
                * private String brokerName;
                * //在这里brokerId=0 代表是Master
                * private HashMap<Long/* brokerId */, String/* broker address */> brokerAddrs;
                */
                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 brokerAddrTable
                // 注意此处是根据传入的brokerId判断是不是需要将此broker进行切换Master
                Iterator<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();
                    }
                }
                
                // 2.2如果存在的话替换原来的
                String oldAddr = brokerData.getBrokerAddrs().put(brokerId, brokerAddr);
                registerFirst = registerFirst || (null == oldAddr);

                // 3.填充topicQueueTable
                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()) {
                                // 可以进入到createAndUpdateQueueData函数去看创建QueueData的逻辑
                                this.createAndUpdateQueueData(brokerName, entry.getValue());
                            }
                        }
                    }
                }

                // 4.更新存活broker列表
                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);
                }

               // 5.注册过滤器列表
                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);
                        }
                    }
                }
            } finally {
                this.lock.writeLock().unlock();
            }
        } catch (Exception e) {
            log.error("registerBroker Exception", e);
        }

        return result;
    }

注:代码中使用的读写锁是并发读的,但是处理心跳包是串行的。

路由删除

路由删除的条件:

1.NameServer每10s扫描brokerLiveTable(HashMap<String, /* brokerAddr */BrokerLiveInfo>(256);),检测每个broker上次的心跳包与当前系统时间的差值,大于120s则移除broker;

2.broker关闭不正常。

其实删除的逻辑和增加的逻辑差不多是反过来的,根据前面NameServe中存储的路由元信息就可以知道大致我们要干嘛。首先根据remoteAddr从brokerLiveTable,filterServerTable移除broker的相关信息,然后维护下broker基础信息,集群信息,topic信息即可。在这里,我们的出发点是remoteAddr。

    public void onChannelDestroy(String remoteAddr, Channel channel) {
        String brokerAddrFound = null;
        if (channel != null) {
            try {
                try {
                    //1 。申请读锁,看看broker是不是存在
                    this.lock.readLock().lockInterruptibly();
                    Iterator<Entry<String, BrokerLiveInfo>> itBrokerLiveTable =
                        this.brokerLiveTable.entrySet().iterator();
                    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 {
                    // 2.申请写锁,开始移除。。
                    this.lock.writeLock().lockInterruptibly();
                   
                    // 3.从brokerLiveTable和filterServerTable中移除
                    this.brokerLiveTable.remove(brokerAddrFound);
                    this.filterServerTable.remove(brokerAddrFound);
                    String brokerNameFound = null;
                    boolean removeBrokerName = false;
                    // 4.维护BrokerAddrTable
                    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());
                        }
                    }

                    // 5.从cluser集群中移除broker,然后看看是不是只有一个broker,是的话把集群也移除
                    if (brokerNameFound != null && removeBrokerName) {
                        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);

                                if (brokerNames.isEmpty()) {
                                    log.info("remove the clusterName[{}] from clusterAddrTable, because channel destroyed and no broker in this cluster",
                                        clusterName);
                                    it.remove();
                                }

                                break;
                            }
                        }
                    }

                    // 6.移除包含当前broker的队列,如果topic中只有当前broker的队列,将topic也移除
                    if (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();
                                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 {
                    this.lock.writeLock().unlock();
                }
            } catch (Exception e) {
                log.error("onChannelDestroy Exception", e);
            }
        }
    }

操作topic是根据brokerName进行的,QueueData含有的属性:

    private String brokerName;
    private int readQueueNums;
    private int writeQueueNums;
    private int perm;
    private int topicSynFlag;

 

疑问:等找到答案再来解答。。

  1. 话说NameServer也是集群,读源码的时候咋就没感觉到集群的存在意义,
  2. 在registerBroker函数中的第三步是要进行master判断才进行创建topic元数据??
  3. 路由删除的channel是干嘛的,点进源码看是netty通道?
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值