Rocketmq之消息隊列分配策略算法實現的源碼分析

Rocketmq之消息隊列分配策略算法實現的源碼分析

本文中包含下面的內容

  • 平均分配策略(默認)(AllocateMessageQueueAveragely)
  • 環形分配策略(AllocateMessageQueueAveragelyByCircle)
  • 手動配置分配策略(AllocateMessageQueueByConfig)
  • 機房分配策略(AllocateMessageQueueByMachineRoom)
  • 一致性哈希分配策略(AllocateMessageQueueConsistentHash)

一、平均分配策略(AllocateMessageQueueAveragely)

下面是Rocketmq中 AllocateMessageQueueAveragely 的源碼

public List<MessageQueue> allocate(String consumerGroup, String currentCID, List<MessageQueue> mqAll,List<String> cidAll) {
        //省略參數校驗、當前消費者id是否存在的校驗
        //走到下面的代碼, 說明參數校驗通過
        int index = cidAll.indexOf(currentCID);
        int mod = mqAll.size() % cidAll.size();
        int averageSize =
            mqAll.size() <= cidAll.size() ? 1 : (mod > 0 && index < mod ? mqAll.size() / cidAll.size()
                + 1 : mqAll.size() / cidAll.size());
        int startIndex = (mod > 0 && index < mod) ? index * averageSize : index * averageSize + mod;
        int range = Math.min(averageSize, mqAll.size() - startIndex);
        for (int i = 0; i < range; i++) {
            result.add(mqAll.get((startIndex + i) % mqAll.size()));
        }
        return result;
    }

對代碼分析如下:

  • 第4行, 計算當前消費者在消費者集合(List<String> cidAll)中下標的位置(index)
  • 第5行, 計算當前消息隊列(Message Queue)中的消息是否能被消費者集合(cidAll)平均消費掉
  • 第6-8行, 計算當前消費者消費的平均數量
    • mqAll.size() <= cidAll.size() ? 1 如果消費者的數量 >= 消息的數量, 當前消費者消耗的消息數量爲1
    • mod > 0 && index < mod ? mqAll.size() / cidAll.size() + 1 : mqAll.size() / cidAll.size() 如果消息不能被消費者平均消費掉, 且當前消費者在消費者集合中的下標(index) < 平均消費後的餘數mod , 則當前消費者消費的數量爲 mqAll.size() / cidAll.size() + 1 , 否則是 mqAll.size() / cidAll.size()
  • 第9行,計算當前消費者開始消費消息的下標
    • 如果消息不能被平均消費掉, 且當前消費者在消費者集合中的下標 < 平均消費後的餘數mod , 則消息開始的下標爲index * averageSize , 否則爲index * averageSize + mod
      第10行, 根據Math.min()計算消費者最終需要消費的數量
  • 第11 - 14 行, 從startIndex開始的下標位置,加載數量爲range的消息到result集合中,最後返回這個result

二、環形分配策略(AllocateMessageQueueAveragelyByCircle)

下面是Rocketmq中 AllocateMessageQueueAveragelyByCircle的源碼

public List<MessageQueue> allocate(String consumerGroup, String currentCID, List<MessageQueue> mqAll, List<String> cidAll) {
        //省略參數校驗、當前消費者id是否存在的校驗
        //走到下面的代碼, 說明參數校驗通過
        int index = cidAll.indexOf(currentCID);
        for (int i = index; i < mqAll.size(); i++) {
            if (i % cidAll.size() == index) {
                result.add(mqAll.get(i));
            }
        }
        return result;
    }

對代碼分析如下:

  • 第2-3行省略了參數校驗、當前消費者是否存在的部分代碼
  • 第4行, 計算當前消費者在消費者集合(List<String>)中的下標(index)
  • 第5-10行,遍歷消息的下標, 對下標取模(mod), 如果與index相等, 則存儲到result集合中,最後返回該集合 。

針對知識點一、二可以通過下面的圖示進行進一步瞭解
普通消費和環形消費比較

圖1、普通消費和環形消費比較

再舉個例子:
    假設有三個消費者、八個消息, 對普通分配方式和環形分配方式,分別如下:

  • 普通消費方式
Message Queue ConsumerId
消息隊列[0] Consumer[0]
消息隊列[1] Consumer[0]
消息隊列[2] Consumer[0]
消息隊列[3] Consumer[1]
消息隊列[4] Consumer[1]
消息隊列[5] Consumer[1]
消息隊列[6] Consumer[2]
消息隊列[7] Consumer[2]

- 環形消費方式

Message Queue ConsumerId
消息隊列[0] Consumer[0]
消息隊列[1] Consumer[1]
消息隊列[2] Consumer[2]
消息隊列[3] Consumer[0]
消息隊列[4] Consumer[1]
消息隊列[5] Consumer[2]
消息隊列[6] Consumer[0]
消息隊列[7] Consumer[1]

三、手動配置策略(AllocateMessageQueueByConfig)

下面是手動配置的代碼

public class AllocateMessageQueueByConfig implements AllocateMessageQueueStrategy {
    private List<MessageQueue> messageQueueList;

    @Override
    public List<MessageQueue> allocate(String consumerGroup, String currentCID, List<MessageQueue> mqAll,
        List<String> cidAll) {
        return this.messageQueueList;
    }

    @Override
    public String getName() {
        return "CONFIG";
    }

    public List<MessageQueue> getMessageQueueList() {
        return messageQueueList;
    }

    public void setMessageQueueList(List<MessageQueue> messageQueueList) {
        this.messageQueueList = messageQueueList;
    }
}

代碼分析:
    進行分配的核心方法是allocate(), 從代碼中可以看出分配的方式是從配置文件中獲取相關的信息, 這中方式自己用的比較少,暫時忽略,後面有研究會進行相關內容更新。

四、機房分配策略(AllocateMessageQueueByMachineRoom)

下面是機房分配策略的代碼

public List<MessageQueue> allocate(String consumerGroup, String currentCID, List<MessageQueue> mqAll,
        List<String> cidAll) {
        List<MessageQueue> result = new ArrayList<MessageQueue>();
        int currentIndex = cidAll.indexOf(currentCID);
        if (currentIndex < 0) {
            return result;
        }
        List<MessageQueue> premqAll = new ArrayList<MessageQueue>();
        for (MessageQueue mq : mqAll) {
            String[] temp = mq.getBrokerName().split("@");
            if (temp.length == 2 && consumeridcs.contains(temp[0])) {
                premqAll.add(mq);
            }
        }

        int mod = premqAll.size() / cidAll.size();
        int rem = premqAll.size() % cidAll.size();
        int startIndex = mod * currentIndex;
        int endIndex = startIndex + mod;
        for (int i = startIndex; i < endIndex; i++) {
            result.add(mqAll.get(i));
        }
        if (rem > currentIndex) {
            result.add(premqAll.get(currentIndex + mod * cidAll.size()));
        }
        return result;
    }
  • 第4-7行, 計算當前消費者在消費者集合中的下標(index), 如果下標 < 0 , 則直接返回
  • 第8-14行, 根據brokerName解析出所有有效機房信息(其實是有效mq), 用Set集合去重, 結果存儲在premqAll中
  • 第16行, 計算消息整除的平均結果mod
  • 第17行, 計算消息是否能夠被平均消費rem,(即消息平均消費後還剩多少消息(remaing))
  • 第18行, 計算當前消費者開始消費的下標(startIndex)
  • 第19行, 計算當前消費者結束消費的下標(endIndex)
  • 第20-26行, 將消息的消費分爲兩部分, 第一部分 – (cidAllSize * mod) , 第二部分 – (premqAll - cidAllSize * mod) ; 從第一部分中查詢startIndex ~ endIndex之間所有的消息, 從第二部分中查詢 currentIndex + mod * cidAll.size() , 最後返回查詢的結果result

可以通過下面的例子進一步瞭解,假設有三個消費者, 八個消息隊列

Message Queue Consumer
消息隊列[0] Consumer[0]
消息隊列[1] Consumer[0]
消息隊列[2] Consumer[1]
消息隊列[3] Consumer[1]
消息隊列[4] Consumer[2]
消息隊列[5] Consumer[2]
消息隊列[6] Consumer[0]
消息隊列[7] Consumer[1]

五、一致性哈希分配策略(AllocateMessageQueueConsistentHash)

下面是一致性哈希算法的代碼

public List<MessageQueue> allocate(String consumerGroup, String currentCID, List<MessageQueue> mqAll, List<String> cidAll) {
        //省略參數校驗、當前消費者id是否存在的校驗
        //走到下面的代碼, 說明參數校驗通過
        Collection<ClientNode> cidNodes = new ArrayList<ClientNode>();
        for (String cid : cidAll) {
            cidNodes.add(new ClientNode(cid));
        }

        final ConsistentHashRouter<ClientNode> router; //for building hash ring
        if (customHashFunction != null) {
            router = new ConsistentHashRouter<ClientNode>(cidNodes, virtualNodeCnt, customHashFunction);
        } else {
            router = new ConsistentHashRouter<ClientNode>(cidNodes, virtualNodeCnt);
        }

        List<MessageQueue> results = new ArrayList<MessageQueue>();
        for (MessageQueue mq : mqAll) {
            ClientNode clientNode = router.routeNode(mq.toString());
            if (clientNode != null && currentCID.equals(clientNode.getKey())) {
                results.add(mq);
            }
        }

        return results;

    }

關於一致性哈希算法的講解,可以通過下面的連接進行了解
https://blog.csdn.net/xianghonglee/article/details/25718099
https://blog.csdn.net/sparkliang/article/details/5279393
https://akshatm.svbtle.com/consistent-hash-rings-theory-and-implementation
https://github.com/gholt/ring/blob/master/BASIC_HASH_RING.md

代碼分析:
    目前對一致性哈希的瞭解還是停留在表明上,暫時不進行分析,後面有深入瞭解再填充這部分內容

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章