Rocketmq - underlying principle of sequential message

Posted by IWS on Thu, 10 Feb 2022 08:03:51 +0100

Rocketmq can only guarantee local order, not global order messages

Sequential consumption based on queue (partition) is provided in RocketMQ.

 

Sequencing in RocketMQ mainly refers to the sequential consumption of messages. Each consumption group in RocketMQ has a separate thread pool for concurrent consumption of pulled messages, that is, the consumer is multi-threaded consumption. The concurrency of sequential consumption is equal to the number of queues allocated to the consumer.

The completion sequence of RokcetMQ is mainly realized by three trivial tasks. The following figure shows the working principle of RocketMQ sequential consumption:

 

1. When starting up, the consumer will first implement the queue load mechanism, following the principle that a consumer can allocate multiple queues, but a queue will only be consumed by one consumer.

2. According to the allocated queue, the consumer applies to the Broker for trivial. If the application is trivial, it will pull the message. Otherwise, it will give up the message pull and try again in the next queue load cycle (20s).

public void start() {
        if (MessageModel.CLUSTERING.equals(ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.messageModel())) {
            this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
//                    Lock consumption queue = >
                    ConsumeMessageOrderlyService.this.lockMQPeriodically();
                }
         }, 1000 * 1, ProcessQueue.REBALANCE_LOCK_INTERVAL, TimeUnit.MILLISECONDS);
    }
}
try{
//      Batch lock message queue = >
        Set<MessageQueue> lockOKMQSet =
        this.mQClientFactory.getMQClientAPIImpl().lockBatchMQ(findBrokerResult.getBrokerAddr(), requestBody, 1000);
        for (MessageQueue mq : lockOKMQSet) {
            ProcessQueue processQueue = this.processQueueTable.get(mq);
            if (processQueue != null) {
                if (!processQueue.isLocked()) {
                    log.info("the message queue locked OK, Group: {} {}", this.consumerGroup, mq);
                }
//      Lock processing queue
                processQueue.setLocked(true);
                processQueue.setLastLockTimestamp(System.currentTimeMillis());
            }
        }
}

3. After the message is pulled, it will be consumed in the thread pool of the consumer. However, when consuming, the consumption queue will be locked, that is, multiple messages in the same consumption queue will be executed serially.

class ConsumeRequest implements Runnable {
        private final ProcessQueue processQueue;
        private final MessageQueue messageQueue;


        public ConsumeRequest(ProcessQueue processQueue, MessageQueue messageQueue) {
            this.processQueue = processQueue;
            this.messageQueue = messageQueue;
        }


        @Override
        public void run() {
            if (this.processQueue.isDroped()) {
                log.warn("run, the message queue not be able to consume, because it's dropped. {}",
                    this.messageQueue);
                return;
            }

            // Ensure the serial consumption of the same queue in the current Consumer
            final Object objLock = messageQueueLock.fetchLockObject(this.messageQueue);
            synchronized (objLock) {
                // Ensure serial consumption in the same queue in the Consumer cluster
                if (MessageModel.BROADCASTING
                    .equals(ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.messageModel())
                        || (this.processQueue.isLocked() && !this.processQueue.isLockExpired())) {
                    final long beginTime = System.currentTimeMillis();
                    for (boolean continueConsume = true; continueConsume;) {
                        if (this.processQueue.isDroped()) {
                            log.warn("the message queue not be able to consume, because it's dropped. {}",
                                this.messageQueue);
                            break;
                        }

                        if (MessageModel.CLUSTERING
                            .equals(ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl
                                .messageModel())
                                && !this.processQueue.isLocked()) {
                            log.warn("the message queue not locked, so consume later, {}", this.messageQueue);
                            ConsumeMessageOrderlyService.this.tryLockLaterAndReconsume(this.messageQueue,
                                this.processQueue, 10);
                            break;
                        }

                        if (MessageModel.CLUSTERING
                            .equals(ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl
                                .messageModel())
                                && this.processQueue.isLockExpired()) {
                            log.warn("the message queue lock expired, so consume later, {}",
                                this.messageQueue);
                            ConsumeMessageOrderlyService.this.tryLockLaterAndReconsume(this.messageQueue,
                                this.processQueue, 10);
                            break;
                        }

                        // When the number of online trips is less than the number of queues, individual queues can be prevented from starvation
                        long interval = System.currentTimeMillis() - beginTime;
                        if (interval > MaxTimeConsumeContinuously) {
                            // Consumption after 10ms
                            ConsumeMessageOrderlyService.this.submitConsumeRequestLater(processQueue,
                                messageQueue, 10);
                            break;
                        }

                        final int consumeBatchSize =
                                ConsumeMessageOrderlyService.this.defaultMQPushConsumer
                                    .getConsumeMessageBatchMaxSize();

                        List<MessageExt> msgs = this.processQueue.takeMessags(consumeBatchSize);
                        if (!msgs.isEmpty()) {
                            final ConsumeOrderlyContext context =
                                    new ConsumeOrderlyContext(this.messageQueue);

                            ConsumeOrderlyStatus status = null;

                            // Execute Hook
                            ConsumeMessageContext consumeMessageContext = null;
                            if (ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.hasHook()) {
                                consumeMessageContext = new ConsumeMessageContext();
                                consumeMessageContext
                                    .setConsumerGroup(ConsumeMessageOrderlyService.this.defaultMQPushConsumer
                                        .getConsumerGroup());
                                consumeMessageContext.setMq(messageQueue);
                                consumeMessageContext.setMsgList(msgs);
                                consumeMessageContext.setSuccess(false);
                                ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl
                                    .executeHookBefore(consumeMessageContext);
                            }

                            long beginTimestamp = System.currentTimeMillis();
                            //Processing queue locking
                            try {
                                this.processQueue.getLockConsume().lock();
                                if (this.processQueue.isDroped()) {
                                    log.warn(
                                        "consumeMessage, the message queue not be able to consume, because it's dropped. {}",
                                        this.messageQueue);
                                    break;
                                }

                                status =
                                        messageListener.consumeMessage(Collections.unmodifiableList(msgs),
                                            context);
                            }
                            catch (Throwable e) {
                                log.warn("consumeMessage exception: {} Group: {} Msgs: {} MQ: {}",//
                                    RemotingHelper.exceptionSimpleDesc(e),//
                                    ConsumeMessageOrderlyService.this.consumerGroup,//
                                    msgs,//
                                    messageQueue);
                            }
                            finally {
                                this.processQueue.getLockConsume().unlock();
                            }

                            // Print log for exception return code
                            if (null == status //
                                    || ConsumeOrderlyStatus.ROLLBACK == status//
                                    || ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT == status) {
                                log.warn("consumeMessage Orderly return not OK, Group: {} Msgs: {} MQ: {}",//
                                    ConsumeMessageOrderlyService.this.consumerGroup,//
                                    msgs,//
                                    messageQueue);
                            }

                            long consumeRT = System.currentTimeMillis() - beginTimestamp;

                            // The queue is suspended when the user throws an exception or returns null
                            if (null == status) {
                                status = ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
                            }

                            // Execute Hook
                            if (ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.hasHook()) {
                                consumeMessageContext.setStatus(status.toString());
                                consumeMessageContext.setSuccess(ConsumeOrderlyStatus.SUCCESS == status
                                        || ConsumeOrderlyStatus.COMMIT == status);
                                ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl
                                    .executeHookAfter(consumeMessageContext);
                            }

                            // Record statistics
                            ConsumeMessageOrderlyService.this.getConsumerStatsManager().incConsumeRT(
                                ConsumeMessageOrderlyService.this.consumerGroup, messageQueue.getTopic(),
                                consumeRT);

                            continueConsume =
                                    ConsumeMessageOrderlyService.this.processConsumeResult(msgs, status,
                                        context, this);
                        }
                        else {
                            continueConsume = false;
                        }
                    }
                }
                // You haven't got the lock of the current queue. You can consume it later
                else {
                    if (this.processQueue.isDroped()) {
                        log.warn("the message queue not be able to consume, because it's dropped. {}",
                            this.messageQueue);
                        return;
                    }

                    ConsumeMessageOrderlyService.this.tryLockLaterAndReconsume(this.messageQueue,
                        this.processQueue, 100);
                }
            }
        }


        public ProcessQueue getProcessQueue() {
            return processQueue;
        }


        public MessageQueue getMessageQueue() {
            return messageQueue;
        }
    }

4. In the process of consumption, the proccessqueue will be locked to ensure that the consumption of messages in processing is completed. After the queue load occurs, other consumers can continue to consume.

What's the use of the last one?

For example, queue q3 is currently allocated to consumer C2 for consumption. 32 messages have been pulled and processed in the process pool, and then the capacity of consumers has been expanded. The q3 queue allocated to C2 is allocated to C3. Because C2 has processed part of the processing, the location information has not been submitted. If C3 immediately consumes the messages in q3 queue, some data will be consumed repeatedly, Therefore, when C2 consumers consume the q3 queue, if the message consumption is not completed, the load queue cannot discard the queue, and the trivial will not be released on the broker side. Other consumers cannot consume from the queue, so as to ensure the repeated consumption of messages and sequential semantics as much as possible.


 

Topics: message queue