RocketMQ-负载均衡

RocketMQ是分布式消息服务,生产和消费负载均衡都是在客户端完成,本文分别介绍生产与消费的负载均衡策略。

部署结构拓扑图

一、路由信息

     路由记录了broker集群节点的通讯地址、broker名称和读写队列数量等信息。

     写队列writequque表示生产者可以写入的队列个数,如果不做特别配置默认是4,队列号从0开始,如果是4个,queueId就是0,1,2,3。broker收到消息后根据queueId生成消息队列。生产者负载均衡的过程的实质就是选择broker集群和queueId的过程。

        读队列readqueue表示broker中可以供消费者读去消息的队列个数,默认也是4个,队列号从0开始。消费者拿到路由信息后会选择queueId,从对应的broker中读取数据消费。

         路由信息主要数据结构有TopicRouteData、QueueData、BrokerData。QueueData与broker集群对应,一个broker集群有一个QueueData对象,里面记录了broker名称、写队列个数与读队列个数。BrokerData记录broker集群所有节点信息,如所有节点的ip、brokerid(每个节点都有一个brokerId,brokerId是0表示master节点)和brokerName。TopicRouteData对象表示与某一个topic有关系的broker节点信息,内部包含多个QueueData对象(可以有多个broker集群支持该topic)和多个BrokerData信息(多个集群的多个节点信息都在该列表中)。

public class BrokerData implements Comparable<BrokerData> {
    private String cluster;
    private String brokerName;
    private HashMap<Long/* brokerId */, String/* broker address */> brokerAddrs;
    //后面省略
}
public class QueueData implements Comparable<QueueData> {
    private String brokerName;
    private int readQueueNums;
    private int writeQueueNums;
    private int perm;
    private int topicSynFlag;
    //后面省略   
}
public class TopicRouteData extends RemotingSerializable {
    private String orderTopicConf;
    private List<QueueData> queueDatas;
    private List<BrokerData> brokerDatas;
    private HashMap<String/* brokerAddr */, List<String>/* Filter Server */> filterServerTable;
    //后面省略
}

       以上是路由的原始信息,客户端获取到后会在本地加工TopicPublishInfo对象,内部主要是根据QueueData生成MessageQueue列表,如在物理拓扑图中,两个master节点,写队列都是默认4,QueueData列表就包含8个对象。

public class TopicPublishInfo {
    private boolean orderTopic = false;
    private boolean haveTopicRouterInfo = false;
    private List<MessageQueue> messageQueueList = new ArrayList<MessageQueue>();
    private volatile ThreadLocalIndex sendWhichQueue = new ThreadLocalIndex();
    private TopicRouteData topicRouteData;
    //后面省略
}

 

public class MessageQueue implements Comparable<MessageQueue>, Serializable {
    private static final long serialVersionUID = 6191200464116433425L;
    private String topic;
    private String brokerName;
    private int queueId;
    //后面省略
}

      MessageQueue队列中的对象顺序有两种,一种是根据指定broker集群顺序生成,如物理拓扑图中,假设顺序是broker2在broker1前面,则生成的对象顺序是broker2的0,1,2,3,然后是broker1的0,1,2,3,数字表示MessageQueue对象中的queueId。另一种是根据brokerName的自然顺序排序,如物理拓扑图的例子,broker1的自然排序在broker2之前,所以MessageQueue队列中的顺序是broker1的0,1,2,3,然后是broker2的0,1,2,3。

 public static TopicPublishInfo topicRouteData2TopicPublishInfo(final String topic, final TopicRouteData route) {
        TopicPublishInfo info = new TopicPublishInfo();
        info.setTopicRouteData(route);
        //broker节点存在顺序关系
        if (route.getOrderTopicConf() != null && route.getOrderTopicConf().length() > 0) {
            String[] brokers = route.getOrderTopicConf().split(";");
            for (String broker : brokers) {
                String[] item = broker.split(":");
                int nums = Integer.parseInt(item[1]);
                for (int i = 0; i < nums; i++) {
                    MessageQueue mq = new MessageQueue(topic, item[0], i);
                    info.getMessageQueueList().add(mq);
                }
            }

            info.setOrderTopic(true);
        } else {
          //broker节点间无顺序关系
            List<QueueData> qds = route.getQueueDatas();
            //queueData按照borkername排序
            Collections.sort(qds);
            for (QueueData qd : qds) {
                if (PermName.isWriteable(qd.getPerm())) {
                    BrokerData brokerData = null;
                    for (BrokerData bd : route.getBrokerDatas()) {
                        if (bd.getBrokerName().equals(qd.getBrokerName())) {
                            brokerData = bd;
                            break;
                        }
                    }

                    if (null == brokerData) {
                        continue;
                    }

                    if (!brokerData.getBrokerAddrs().containsKey(MixAll.MASTER_ID)) {
                        continue;
                    }

                    for (int i = 0; i < qd.getWriteQueueNums(); i++) {
                        MessageQueue mq = new MessageQueue(topic, qd.getBrokerName(), i);
                        info.getMessageQueueList().add(mq);
                    }
                }
            }

            info.setOrderTopic(false);
        }

        return info;
    }

 

2、消息生产负载均衡

    生产者负载均衡实质上是在选择MessageQueue对象(内部包含brokerName与queueId),一种是默认策略,从MessageQueue列表中随机选择一个,实现过程是通过自增随机数对列表大小取余获取位置信息,但获得的MessageQueue所在的集群不能是上次的失败集群。另一种是集群超时容忍策略,先随机选择一个MessageQueue,如果因为超时等异常发送失败,会优先选择该broker集群下其他的messeagequeue进行发送;如果没有找到则从之前发送失败broker集群中选择一个MessageQueue进行发送;如果还没有找到则使用默认策略

 public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName) {
        //开始集群超时容忍
        if (this.sendLatencyFaultEnable) {
            try {
                int index = tpInfo.getSendWhichQueue().getAndIncrement();
                for (int i = 0; i < tpInfo.getMessageQueueList().size(); i++) {
                    int pos = Math.abs(index++) % tpInfo.getMessageQueueList().size();
                    if (pos < 0)
                        pos = 0;
                    MessageQueue mq = tpInfo.getMessageQueueList().get(pos);
                    //1、如果上一次发送该集群超时失败,选取时仍然会先选择相同集群下的其他MessageQueue
                    if (latencyFaultTolerance.isAvailable(mq.getBrokerName())) {
                        if (null == lastBrokerName || mq.getBrokerName().equals(lastBrokerName))
                            return mq;
                    }
                }
                
                //2、从之前失败过的列表中选择一个,该列表的节点根据是否可用,超时时间和新近可用时间做了排序
                final String notBestBroker = latencyFaultTolerance.pickOneAtLeast();
                int writeQueueNums = tpInfo.getQueueIdByBroker(notBestBroker);
                if (writeQueueNums > 0) {
                    final MessageQueue mq = tpInfo.selectOneMessageQueue();
                    if (notBestBroker != null) {
                        mq.setBrokerName(notBestBroker);
                        mq.setQueueId(tpInfo.getSendWhichQueue().getAndIncrement() % writeQueueNums);
                    }
                    return mq;
                } else {
                    latencyFaultTolerance.remove(notBestBroker);
                }
            } catch (Exception e) {
                log.error("Error occurred when selecting message queue", e);
            }
            //3、如果1、2步都没有选出则走默认策略
            return tpInfo.selectOneMessageQueue();
        }
        //默认策略
        return tpInfo.selectOneMessageQueue(lastBrokerName);
    }

点评:当broker集群A接收数据超时后,生产者采用默认策略会选出另一个broker集群B,B的流量会瞬间出现峰值,给服务带来抖动,因此超时容忍策略的作用就会突出,虽然短时间内发送会失败,但不会对其他集群带来波峰流量

三、消费负载均衡

 1、AllocateMessageQueueAveragely算法

      MessageQueue列表是topic下可以拉去消息的队列,消费客户端是订阅topic的消费者,当消息队列个数小于可消费客户端时,消息队列与客户端对应情况如左侧图;当消息队列个数大于可消费客户端时,消息队列与客户端对应情况如右侧图。

 

 

AllocateMessageQueueAveragely算法

 

2、AllocateMessageQueueAveragelyByCircle算法

AllocateMessageQueueAveragelyByCircle算法

  3、AllocateMachineRoomNearby算法

       同机房分配策略,首先统计消费者与broker所在机房,保证broker中的消息优先被同机房的消费者消费,如果机房中没有消费者,则有其他机房消费者消费。实际的队列分配(同机房或跨机房)可以是指定其他算法。假设有三个机房,实际负载策略使用算法1,机房1和机房3中存在消费者,机房2没有消费者。机房1、机房3中的队列会分配给各自机房中的消费者,机房2的队列会被虽有的消费者平均分配。

AllocateMachineRoomNearby算法

4、AllocateMessageQueueByMachineRoom算法

只消费特定broker中的消息,如下图所示,第一张图是消费者小于队列数情况,第二张图是消费者多余队列数情况。假设有三个机房,配置机房三不在消费者的服务范围内,则实际消费对应关系如下两图所示。

AllocateMessageQueueByMachineRoom算法
AllocateMessageQueueByMachineRoom算法

5、AllocateMessageQueueByConfig算法

     该算法比较简单,在客户端直接配置可消费的messageQueueList,指定规定的消息队列

6、AllocateMessageQueueConsistentHash算法

   使用一致性哈希算法进行负载,每次负载都会重新创建一致性hash路由表,获取本地客户端负责的所有队列信息。RocketMQ默认的hash算法为MD5。假设有4个客户端的clientId和两个消息队列mq1,mq2,,通过hash后分布在hash环的不同位置,按照一致性hash的顺时针查找原则,mq1被client2消费,mq2被client3消费。

AllocateMessageQueueConsistentHash算法
已标记关键词 清除标记
RocketMQ 是阿里巴巴在2012年开源的分布式消息中间件,目前已经捐赠给 Apache 软件基金会,并于2017年9月25日成为Apache 的顶级项目。作为经历过多次阿里巴巴双十一这种“超级工程”的洗礼并有稳定出色表现的国产中间件,以其高性能、低延时和高可靠等特性近年来已经也被越来越多的国内企业使用。其主要功能有1.灵活可扩展性、2.海量消息堆积能力、3.支持顺序消息、4.多种消息过滤方式、5.支持事务消息、6.回溯消费等常用功能。 RocketMQ 核心的四大组件:Name Server、Broker、Producer、Consumer ,每个组件都可以部署成集群进行水平扩展。 2、适应人群 有一定的Java基础,并且有分布式项目开发经验。 3、课程价值 可以让初学者对分布式系统解耦有一定认识,并且能够通过快速使用RocketMQ实现分布式服务的异步通信,同时本课程还会通过项目案例实战让学员对RocketMQ的应用场景有所体会,最后再通过源码角度让学员对RocketMQ的原理有所理解,不仅做到“知其然”,亦“知其所以然”。 4、课程收获 1. 理解消息中间件MQ的优势和应用场景 2. 掌握RocketMQ的核心功能,以及各种消息发送案例 3. 通过电商项目深刻理解RocketMQ在使用项目中的落地应用 4. 通过RocketMQ高级功能和源码学习,对RocketMQ的技术细节和原理有更加透彻的理解 5、课程亮点 l  核心功能 n  MQ介绍 n  环境准备 n  RocketMQ高可用集群搭建 n  各种消息发送样例 l  综合练习 n  项目背景介绍 n  功能分析 n  项目环境搭建 n  下单功能,保证各服务的数据一致性 n  确认订单功能,通过消息进行数据分发 n  整体联调 l  高级功能 n  消息的存储和发送 n  消息存储结构 n  刷盘机制 n  消息的同步复制和异步复制 n  负载均衡 l  源码分析 n  路由中心NameServer n  消息生产者Producer n  消息存储 n  消息消费Consumer 6、主讲内容 章节一:核心功能 1.     快速入门 a)     MQ介绍 b)     作用 c)      注意事项 d)     各MQ产品比较 2.     RocketMQ环境搭建 a)     环境准备 b)     安装RocketMQ c)      启动RocketMQ d)     测试RocketMQ e)     关闭RocketMQ 3.     RocketMQ高可用集群搭建 a)     集群各角色介绍 b)     集群搭建方式 c)      双主双从集群搭建 d)     集群监控平台 4.     各种消息发送样例 a)     同步消息 b)     异步消息 c)      单向消息 d)     顺序消息 e)     批量消息 f)      过滤消息 g)     事务消息 章节二:项目实战 1.    项目背景介绍 (1)    电商高可用MQ实战 2.    功能分析 (1)    下单功能 (2)    支付功能 3.    项目环境搭建 (1)    SpringBoot (2)    Dubbo (3)    Zookeeper (4)    RocketMQ (5)    Mysql 4.下单功能,保证各服务的数据一致性 5.确认订单功能,通过消息进行数据分发 章节三:高级功能 1. 消息的存储和发送 2. 消息存储结构 3. 刷盘机制 (1)    同步刷盘 (2)    异步刷盘 4. 消息的同步复制和异步复制 5. 负载均衡 (1)    Producer负载均衡 (2)    Consumer负载均衡 章节四:源码分析 1.     路由中心NameServer a)     NameServer架构设计 b)     NameServer启动流程 c)      NameServer路由注册和故障剔除 2.     消息生产者Producer a)     生产者启动流程 b)     生产者发送消息流程 c)      批量发送 3.     消息存储 a)     消息存储流程 b)     存储文件与内存映射 c)      存储文件 d)     实时更新消息消费队列和存储文件 e)     消息队列与索引文件恢复 f)      刷盘机制 4.     过期文件删除机制 a)     消息消费Consumer b)     消费者启动流程 c)      消息拉取 d)     消息队列负载均衡和重新分布机制 e)     消息消费过程 f)      定时消息机制 g)     顺序消息
ZooKeeper + LevelDB + Static discovery 做activemq的集群负载均衡 在activemq.xml中已经添加了如下的配置 集群a: ``` <networkConnectors> <networkConnector uri="static:(tcp://10.1.60.32:53531,tcp://10.1.60.32:53532,tcp://10.1.60.32:53533)" duplex="false"/> </networkConnectors> ``` 集群b: ``` <networkConnectors> <networkConnector uri="static:(tcp://10.1.60.41:51511,tcp://10.1.60.42:51512,tcp://10.1.60.43:51513)" duplex="false"/> </networkConnectors> ``` 如上是我的配置,我是2个集群各3台activemq,每个单点都做了另外一个集群的networkConnnetor配置,activemq实例启动都是正常的,但是启动后无法通过桥接互相消费消息,报如下的错误,2边的日志中都有: A端的日志报: 2016-05-13 17:24:17,238 | WARN | Failed to add Connection Broker->Broker2-48906-1463129863611-233:1 | org.apache.activemq.broker.TransportConnection | triggerStartAsyncNetworkBridgeCreation: remoteBroker=tcp:///10.1.60.32:53531@51183, localBroker= vm://Broker#346 java.lang.SecurityException: User name [null] or password is invalid. at b端的日志报: 2016-05-13 17:25:25,671 | WARN | Failed to add Connection Broker2->Broker-50573-1463130022715-221:1 | org.apache.activemq.broker.TransportConnection | triggerStartAsyncNetworkBridgeCreation: remoteBroker=tcp:///10.1.60.43:51513@37179, localBroker= vm://Broker2#328 java.lang.SecurityException: User name [null] or password is invalid. at 百度了很久都没有类似的问题得到解决,不知道有没有谁碰到过此类问题?
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页