lizhimins commented on code in PR #9048:
URL: https://github.com/apache/rocketmq/pull/9048#discussion_r1884965674


##########
broker/src/main/java/org/apache/rocketmq/broker/pop/PopConsumerService.java:
##########
@@ -0,0 +1,699 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.broker.pop;
+
+import com.alibaba.fastjson.JSON;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Stopwatch;
+import java.nio.ByteBuffer;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Triple;
+import org.apache.rocketmq.broker.BrokerController;
+import org.apache.rocketmq.common.BrokerConfig;
+import org.apache.rocketmq.common.KeyBuilder;
+import org.apache.rocketmq.common.MixAll;
+import org.apache.rocketmq.common.ServiceThread;
+import org.apache.rocketmq.common.TopicConfig;
+import org.apache.rocketmq.common.TopicFilterType;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.common.constant.PermName;
+import org.apache.rocketmq.common.message.MessageAccessor;
+import org.apache.rocketmq.common.message.MessageConst;
+import org.apache.rocketmq.common.message.MessageDecoder;
+import org.apache.rocketmq.common.message.MessageExt;
+import org.apache.rocketmq.common.message.MessageExtBrokerInner;
+import org.apache.rocketmq.common.utils.ConcurrentHashMapUtils;
+import org.apache.rocketmq.remoting.protocol.header.ExtraInfoUtil;
+import org.apache.rocketmq.store.AppendMessageStatus;
+import org.apache.rocketmq.store.GetMessageResult;
+import org.apache.rocketmq.store.GetMessageStatus;
+import org.apache.rocketmq.store.MessageFilter;
+import org.apache.rocketmq.store.PutMessageResult;
+import org.apache.rocketmq.store.SelectMappedBufferResult;
+import org.apache.rocketmq.store.exception.ConsumeQueueException;
+import org.apache.rocketmq.store.pop.PopCheckPoint;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class PopConsumerService extends ServiceThread {
+
+    private static final Logger log = 
LoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME);
+    private static final long OFFSET_NOT_EXIST = -1L;
+    private static final String ROCKSDB_DIRECTORY = "kvStore";
+    private static final int[] REWRITE_INTERVALS_IN_SECONDS =
+        new int[] {10, 30, 60, 120, 180, 240, 300, 360, 420, 480, 540, 600, 
1200, 1800, 3600, 7200};
+
+    private final AtomicBoolean consumerRunning;
+    private final BrokerConfig brokerConfig;
+    private final BrokerController brokerController;
+    private final AtomicLong lastCleanupLockTime;
+    private final PopConsumerCache popConsumerCache;
+    private final PopConsumerKVStore popConsumerStore;
+    private final PopConsumerLockService consumerLockService;
+    private final ConcurrentMap<String /* groupId@topicId*/, AtomicLong> 
requestCountTable;
+
+    public PopConsumerService(BrokerController brokerController) {
+
+        this.brokerController = brokerController;
+        this.brokerConfig = brokerController.getBrokerConfig();
+
+        this.consumerRunning = new AtomicBoolean(false);
+        this.requestCountTable = new ConcurrentHashMap<>();
+        this.lastCleanupLockTime = new AtomicLong(System.currentTimeMillis());
+        this.consumerLockService = new 
PopConsumerLockService(TimeUnit.MINUTES.toMillis(2));
+        this.popConsumerStore = new PopConsumerRocksdbStore(Paths.get(
+            brokerController.getMessageStoreConfig().getStorePathRootDir(), 
ROCKSDB_DIRECTORY).toString());
+        this.popConsumerCache = brokerConfig.isEnablePopBufferMerge() ? new 
PopConsumerCache(
+            brokerController, this.popConsumerStore, this.consumerLockService, 
this::revive) : null;
+
+        log.info("PopConsumerService init, buffer={}, rocksdb filePath={}",
+            brokerConfig.isEnablePopBufferMerge(), 
this.popConsumerStore.getFilePath());
+    }
+
+    /**
+     * In-flight messages are those that have been received from a queue
+     * by a consumer but have not yet been deleted. For standard queues,
+     * there is a limit on the number of in-flight messages, depending on 
queue traffic and message backlog.
+     */
+    public boolean isPopShouldStop(String group, String topic, int queueId) {
+        return brokerConfig.isEnablePopMessageThreshold() && popConsumerCache 
!= null &&
+            popConsumerCache.getPopInFlightMessageCount(group, topic, queueId) 
>=
+                brokerConfig.getPopInflightMessageThreshold();
+    }
+
+    public long getPendingFilterCount(String groupId, String topicId, int 
queueId) {
+        try {
+            long maxOffset = 
this.brokerController.getMessageStore().getMaxOffsetInQueue(topicId, queueId);
+            long consumeOffset = 
this.brokerController.getConsumerOffsetManager().queryOffset(groupId, topicId, 
queueId);
+            return maxOffset - consumeOffset;
+        } catch (ConsumeQueueException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public GetMessageResult recodeRetryMessage(GetMessageResult 
getMessageResult,
+        String topicId, long offset, long popTime, long invisibleTime) {
+
+        if (getMessageResult.getMessageCount() == 0 ||
+            getMessageResult.getMessageMapedList().isEmpty()) {
+            return getMessageResult;
+        }
+
+        GetMessageResult result = new 
GetMessageResult(getMessageResult.getMessageCount());
+        result.setStatus(GetMessageStatus.FOUND);
+        String brokerName = brokerConfig.getBrokerName();
+
+        for (SelectMappedBufferResult bufferResult : 
getMessageResult.getMessageMapedList()) {
+            List<MessageExt> messageExtList = MessageDecoder.decodesBatch(
+                bufferResult.getByteBuffer(), true, false, true);
+            bufferResult.release();
+            for (MessageExt messageExt : messageExtList) {
+                try {
+                    // When override retry message topic to origin topic,
+                    // need clear message store size to recode
+                    String ckInfo = ExtraInfoUtil.buildExtraInfo(offset, 
popTime, invisibleTime, 0,
+                        messageExt.getTopic(), brokerName, 
messageExt.getQueueId(), messageExt.getQueueOffset());
+                    
messageExt.getProperties().putIfAbsent(MessageConst.PROPERTY_POP_CK, ckInfo);
+                    messageExt.setTopic(topicId);
+                    messageExt.setStoreSize(0);
+                    byte[] encode = MessageDecoder.encode(messageExt, false);
+                    ByteBuffer buffer = ByteBuffer.wrap(encode);
+                    SelectMappedBufferResult tmpResult = new 
SelectMappedBufferResult(
+                        bufferResult.getStartOffset(), buffer, encode.length, 
null);
+                    result.addMessage(tmpResult);
+                } catch (Exception e) {
+                    log.error("PopConsumerService exception in recode retry 
message, topic={}", topicId, e);
+                }
+            }
+        }
+
+        return result;
+    }
+
+    public PopConsumerContext addGetMessageResult(PopConsumerContext context, 
GetMessageResult result,
+        String topicId, int queueId, PopConsumerRecord.RetryType retryType, 
long offset) {
+
+        if (result.getStatus() == GetMessageStatus.FOUND && 
!result.getMessageQueueOffset().isEmpty()) {
+            if (context.isFifo()) {
+                this.setFifoBlocked(context, context.getGroupId(), topicId, 
queueId, result.getMessageQueueOffset());
+            }
+
+            // build request header here
+            context.addGetMessageResult(result, topicId, queueId, retryType, 
offset);
+
+            if (brokerConfig.isPopConsumerKVServiceLog()) {
+                log.info("PopConsumerService pop, time={}, invisible={}, " +
+                        "groupId={}, topic={}, queueId={}, offset={}, 
attemptId={}",
+                    context.getPopTime(), context.getInvisibleTime(), 
context.getGroupId(),
+                    topicId, queueId, result.getMessageQueueOffset(), 
context.getAttemptId());
+            }
+        }
+
+        if (!context.isFifo() && result.getNextBeginOffset() > 
OFFSET_NOT_EXIST) {
+            this.brokerController.getConsumerOffsetManager().commitPullOffset(
+                context.getClientHost(), context.getGroupId(), topicId, 
queueId, result.getNextBeginOffset());
+        }
+
+        return context;
+    }
+
+    public CompletableFuture<GetMessageResult> getMessageAsync(String 
clientHost,
+        String groupId, String topicId, int queueId, long offset, int 
batchSize, MessageFilter filter) {
+
+        log.debug("PopConsumerService getMessageAsync, groupId={}, topicId={}, 
queueId={}, offset={}, batchSize={}, filter={}",
+            groupId, topicId, offset, queueId, batchSize, filter != null);
+
+        CompletableFuture<GetMessageResult> getMessageFuture =
+            brokerController.getMessageStore().getMessageAsync(groupId, 
topicId, queueId, offset, batchSize, filter);
+
+        // refer 
org.apache.rocketmq.broker.processor.PopMessageProcessor#popMsgFromQueue
+        return getMessageFuture.thenCompose(result -> {
+            if (result == null) {
+                return CompletableFuture.completedFuture(null);
+            }
+
+            // maybe store offset is not correct.
+            if (GetMessageStatus.OFFSET_TOO_SMALL.equals(result.getStatus()) ||
+                
GetMessageStatus.OFFSET_OVERFLOW_BADLY.equals(result.getStatus()) ||
+                GetMessageStatus.OFFSET_FOUND_NULL.equals(result.getStatus())) 
{
+
+                // commit offset, because the offset is not correct
+                // If offset in store is greater than cq offset, it will cause 
duplicate messages,
+                // because offset in PopBuffer is not committed.
+                this.brokerController.getConsumerOffsetManager().commitOffset(
+                    clientHost, groupId, topicId, queueId, 
result.getNextBeginOffset());
+
+                log.warn("PopConsumerService getMessageAsync, initial offset 
because store is no correct, " +
+                        "groupId={}, topicId={}, queueId={}, batchSize={}, 
offset={}->{}",
+                    groupId, topicId, queueId, batchSize, offset, 
result.getNextBeginOffset());
+
+                return brokerController.getMessageStore().getMessageAsync(
+                    groupId, topicId, queueId, result.getNextBeginOffset(), 
batchSize, filter);
+            }
+
+            return CompletableFuture.completedFuture(result);
+
+        }).whenComplete((result, throwable) -> {
+            if (throwable != null) {
+                log.error("Pop getMessageAsync error", throwable);
+            }
+        });
+    }
+
+    /**
+     * Fifo message does not have retry feature in broker
+     */
+    public void setFifoBlocked(PopConsumerContext context,
+        String groupId, String topicId, int queueId, List<Long> 
queueOffsetList) {
+        brokerController.getConsumerOrderInfoManager().update(
+            context.getAttemptId(), false, topicId, groupId, queueId,
+            context.getPopTime(), context.getInvisibleTime(), queueOffsetList, 
context.getOrderCountInfoBuilder());
+    }
+
+    public boolean isFifoBlocked(PopConsumerContext context, String groupId, 
String topicId, int queueId) {
+        return brokerController.getConsumerOrderInfoManager().checkBlock(
+            context.getAttemptId(), topicId, groupId, queueId, 
context.getInvisibleTime());
+    }
+
+    protected CompletableFuture<PopConsumerContext> 
getMessageAsync(CompletableFuture<PopConsumerContext> future,
+        String clientHost, String groupId, String topicId, int queueId, int 
batchSize, MessageFilter filter,
+        PopConsumerRecord.RetryType retryType) {
+
+        return future.thenCompose(result -> {
+
+            // pop request too much, should not add rest count here
+            if (isPopShouldStop(groupId, topicId, queueId)) {
+                return CompletableFuture.completedFuture(result);
+            }
+
+            // Current requests would calculate the total number of messages
+            // waiting to be filtered for new message arrival notifications in
+            // the long-polling service, need disregarding the backlog in order
+            // consumption scenario. If rest message num including the blocked
+            // queue accumulation would lead to frequent unnecessary wake-ups
+            // of long-polling requests, resulting unnecessary CPU usage.
+            // When client ack message, long-polling request would be 
notifications
+            // by AckMessageProcessor.ackOrderly() and message will not be 
delayed.
+            if (result.isFifo() && isFifoBlocked(result, groupId, topicId, 
queueId)) {
+                // should not add accumulation(max offset - consumer offset) 
here
+                return CompletableFuture.completedFuture(result);
+            }
+
+            int remain = batchSize - result.getMessageCount();
+            if (remain <= 0) {
+                result.addRestCount(this.getPendingFilterCount(groupId, 
topicId, queueId));
+                return CompletableFuture.completedFuture(result);
+            } else {
+                long consumeOffset = 
brokerController.getConsumerOffsetManager().queryPullOffset(groupId, topicId, 
queueId);
+                return getMessageAsync(clientHost, groupId, topicId, queueId, 
consumeOffset, remain, filter)
+                    .thenApply(getMessageResult -> addGetMessageResult(
+                        result, getMessageResult, topicId, queueId, retryType, 
consumeOffset));
+            }
+        });
+    }
+
+    public CompletableFuture<PopConsumerContext> popAsync(String clientHost, 
long popTime, long invisibleTime,
+        String groupId, String topicId, int queueId, int batchSize, boolean 
fifo, String attemptId,
+        MessageFilter filter) {
+
+        PopConsumerContext popConsumerContext =
+            new PopConsumerContext(clientHost, popTime, invisibleTime, 
groupId, fifo, attemptId);
+
+        TopicConfig topicConfig = 
brokerController.getTopicConfigManager().selectTopicConfig(topicId);
+        if (topicConfig == null || !consumerLockService.tryLock(groupId, 
topicId)) {
+            return CompletableFuture.completedFuture(popConsumerContext);
+        }
+
+        log.debug("PopConsumerService popAsync, groupId={}, topicId={}, 
queueId={}, " +
+                "batchSize={}, invisibleTime={}, fifo={}, attemptId={}, 
filter={}",
+            groupId, topicId, queueId, batchSize, invisibleTime, fifo, 
attemptId, filter);
+
+        String requestKey = groupId + "@" + topicId;
+        String retryTopicV1 = KeyBuilder.buildPopRetryTopicV1(topicId, 
groupId);
+        String retryTopicV2 = KeyBuilder.buildPopRetryTopicV2(topicId, 
groupId);
+        long requestCount = 
Objects.requireNonNull(ConcurrentHashMapUtils.computeIfAbsent(
+            requestCountTable, requestKey, k -> new 
AtomicLong(0L))).getAndIncrement();
+        boolean preferRetry = requestCount % 5L == 0L;
+
+        CompletableFuture<PopConsumerContext> getMessageFuture =
+            CompletableFuture.completedFuture(popConsumerContext);
+
+        try {
+            GetMessageResult finalGetMessageResult = new GetMessageResult();
+            
finalGetMessageResult.setStatus(GetMessageStatus.NO_MESSAGE_IN_QUEUE);
+
+            if (!fifo && preferRetry) {
+                if (brokerConfig.isRetrieveMessageFromPopRetryTopicV1()) {
+                    getMessageFuture = this.getMessageAsync(getMessageFuture, 
clientHost, groupId,
+                        retryTopicV1, 0, batchSize, filter, 
PopConsumerRecord.RetryType.RETRY_TOPIC_V1);

Review Comment:
   In the final version, the v2 format is used only. During the retry topic 
format transfer phase, there may be unconsumed retry messages in the v1 topic.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscr...@rocketmq.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org

Reply via email to