RocketMQ架构






同一个Topic可以在多个Broker中创建,同一个Broker中的某一个Topic可以拥有多个队列。那么如何保证消息在队列中顺序呢?



AllocateMessageQueueStrategy   消费者均衡       MessageQueueSelector  生产者均衡     topicRouteData2TopicPublishInfo



在RocketMQ 中在 org.apache.rocketmq.client.producer.selector 包中,负责Producer发送负载均衡。在创建topic的时候,每一个分区每一个topic都有会topicConf信息。

在topic config中都有writeQueueNum. 在producer 从name获取topic config信息后,会把这个信息转化成MessageQueue信息。在Producer 发送信息时,会通过MessageQueueSelector 来进行负载均衡。


在RocketMQ 中消息只保证在同一个分区同一个Queue队列的顺序性。 所以,要保证业务逻辑的顺序性,可以通过MessageQueue 来完成操作。

Producer发送负载均衡逻辑代码:

//MessageQueue 队列选择器
public interface MessageQueueSelector {
	/**
	 * 选择出发送消息的MessageQueue
	 * @param mqs  与该topic相关联的所有MessageQueue队列
	 * @param msg  发送的消息
	 * @param arg  业务对象
	 * @return   发送消息的消息队里
	 */
    MessageQueue select(final List mqs, final Message msg, final Object arg);
}


RocketMQ 提供几种负载均衡策略: 随机、散列等

public class SelectMessageQueueByRandoom implements MessageQueueSelector {
    private Random random = new Random(System.currentTimeMillis());

    @Override
    public MessageQueue select(List mqs, Message msg, Object arg) {
        int value = random.nextInt();
        if (value < 0) {
            value = Math.abs(value);
        }

        value = value % mqs.size();
        return mqs.get(value);
    }
}


发送负载均衡源代码如下:

    private SendResult sendSelectImpl(//
        Message msg, //
        MessageQueueSelector selector, //
        Object arg, //
        final CommunicationMode communicationMode, //
        final SendCallback sendCallback, final long timeout//
    ) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
        this.makeSureStateOK();
        Validators.checkMessage(msg, this.defaultMQProducer);

        TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());
        if (topicPublishInfo != null && topicPublishInfo.ok()) {
            MessageQueue mq = null;
            try {
                mq = selector.select(topicPublishInfo.getMessageQueueList(), msg, arg);
            } catch (Throwable e) {
                throw new MQClientException("select message queue throwed exception.", e);
            }

            if (mq != null) {
                return this.sendKernelImpl(msg, mq, communicationMode, sendCallback, null, timeout);
            } else {
                throw new MQClientException("select message queue return null.", null);
            }
        }

        throw new MQClientException("No route info for this topic, " + msg.getTopic(), null);
    }



在Consumer端负载均衡,当使用pull  Consumer 时,当订阅topic时会调用subscriptionAutomatically 方法。


    public void subscriptionAutomatically(final String topic) {
        if (!this.rebalanceImpl.getSubscriptionInner().containsKey(topic)) {
            try {
                SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(this.defaultMQPullConsumer.getConsumerGroup(), //
                    topic, SubscriptionData.SUB_ALL);
                this.rebalanceImpl.subscriptionInner.putIfAbsent(topic, subscriptionData);
            } catch (Exception ignore) {
            }
        }
    }

当使用Push Consumer 时,当订阅topic时,会调用subscirbe 方法

    public void subscribe(String topic, String subExpression) throws MQClientException {
        try {
            SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(this.defaultMQPushConsumer.getConsumerGroup(), //
                topic, subExpression);
            this.rebalanceImpl.getSubscriptionInner().put(topic, subscriptionData);
            if (this.mQClientFactory != null) {
                this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();
            }
        } catch (Exception e) {
            throw new MQClientException("subscription exception", e);
        }
    }

无论是Push  Consumer 或者Pull  Consumer 消费者,都会把topic订阅信息发送到RebalanceImpl 消费负载均衡类中。


实质上由RebalanceService 服务类定时执行rebalance方法。


public class RebalanceService extends ServiceThread {
    private static long waitInterval =
        Long.parseLong(System.getProperty(
            "rocketmq.client.rebalance.waitInterval", "20000"));
    private final Logger log = ClientLogger.getLog();
    private final MQClientInstance mqClientFactory;

    public RebalanceService(MQClientInstance mqClientFactory) {
        this.mqClientFactory = mqClientFactory;
    }

    @Override
    public void run() {
        log.info(this.getServiceName() + " service started");

        while (!this.isStopped()) {
            this.waitForRunning(waitInterval);
            this.mqClientFactory.doRebalance();
        }

        log.info(this.getServiceName() + " service end");
    }

    @Override
    public String getServiceName() {
        return RebalanceService.class.getSimpleName();
    }
}



当Consumer获取所有topic config 信息后,RebalanceService进行负载均衡,然后订阅指定的MessageQueue信息。


Consumer端负载均衡代码如下:

    //把同一个topic中不同的MessageQueue 分配给消费端
    private void rebalanceByTopic(final String topic, final boolean isOrder) {
    	//根据模式来分配
        switch (messageModel) {
            //广播模式
            case BROADCASTING: {
            	//在广播模式下,consumer 消费每一个topic信息
                Set mqSet = this.topicSubscribeInfoTable.get(topic);
                if (mqSet != null) {
                    boolean changed = this.updateProcessQueueTableInRebalance(topic, mqSet, isOrder);
                    if (changed) {
                        this.messageQueueChanged(topic, mqSet, mqSet);
                        log.info("messageQueueChanged {} {} {} {}", //
                            consumerGroup, //
                            topic, //
                            mqSet, //
                            mqSet);
                    }
                } else {
                    log.warn("doRebalance, {}, but the topic[{}] not exist.", consumerGroup, topic);
                }
                break;
            }
            //集群模式下
            case CLUSTERING: {
                Set mqSet = this.topicSubscribeInfoTable.get(topic);
                List cidAll = this.mQClientFactory.findConsumerIdList(topic, consumerGroup);
                if (null == mqSet) {
                    if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
                        log.warn("doRebalance, {}, but the topic[{}] not exist.", consumerGroup, topic);
                    }
                }

                if (null == cidAll) {
                    log.warn("doRebalance, {} {}, get consumer id list failed", consumerGroup, topic);
                }
                //把同一个Topic下面的所有MessageQueue 和该Topic下面的所有Client进行分配
                if (mqSet != null && cidAll != null) {
                    List mqAll = new ArrayList();
                    mqAll.addAll(mqSet);
                    //对All MessageQueue 和All Client 进行排序
                    Collections.sort(mqAll);
                    Collections.sort(cidAll);
                    //消费者均衡策略
                    AllocateMessageQueueStrategy strategy = this.allocateMessageQueueStrategy;

                    List allocateResult = null;
                    try {
                    	//执行Consumer端负载均衡策略
                        allocateResult = strategy.allocate(//
                            this.consumerGroup, //
                            this.mQClientFactory.getClientId(), //
                            mqAll, //
                            cidAll);
                    } catch (Throwable e) {
                        log.error("AllocateMessageQueueStrategy.allocate Exception. allocateMessageQueueStrategyName={}", strategy.getName(),
                            e);
                        return;
                    }
                    //分配结果
                    Set allocateResultSet = new HashSet();
                    if (allocateResult != null) {
                        allocateResultSet.addAll(allocateResult);
                    }
                    //对分配结果进行订阅
                    boolean changed = this.updateProcessQueueTableInRebalance(topic, allocateResultSet, isOrder);
                    if (changed) {
                        log.info(
                            "rebalanced result changed. allocateMessageQueueStrategyName={}, group={}, topic={}, clientId={}, mqAllSize={}, cidAllSize={}, rebalanceResultSize={}, rebalanceResultSet={}",
                            strategy.getName(), consumerGroup, topic, this.mQClientFactory.getClientId(), mqSet.size(), cidAll.size(),
                            allocateResultSet.size(), allocateResultSet);
                        this.messageQueueChanged(topic, mqSet, allocateResultSet);
                    }
                }
                break;
            }
            default:
                break;
        }
    }

当MessageQueue 分配后,会执行dispatherPullRequest方法来轮询消息。


    @Override
    public void dispatchPullRequest(List pullRequestList) {
        for (PullRequest pullRequest : pullRequestList) {
            this.defaultMQPushConsumerImpl.executePullRequestImmediately(pullRequest);
            log.info("doRebalance, {}, add a new pull request {}", consumerGroup, pullRequest);
        }
    }



在org.apache.rocketmq.client.consumer 包中AllocateMessageQueueStrategy 类在负载分配topic中所有MessageQueue 与所有客户端的关系。


/**
 * Strategy Algorithm for message allocating between consumers
 */
public interface AllocateMessageQueueStrategy {

    /**
     * Allocating by consumer id
     *
     * @param consumerGroup current consumer group
     * @param currentCID current consumer id
     * @param mqAll message queue set in current topic
     * @param cidAll consumer set in current consumer group
     * @return The allocate result of given strategy
     */
    List allocate(
        final String consumerGroup,
        final String currentCID,
        final List mqAll,
        final List cidAll
    );

    /**
     * Algorithm name
     *
     * @return The strategy name
     */
    String getName();
}


你可能感兴趣的:(RocketMQ架构)