RocketMQ 什么时候创建 Dead letter topic

802 阅读1分钟

RocketMQ Dead letter topic

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第11天,点击查看活动详情

什么是 Dead Letter Topic

Dead letter topic 是对 retry topic的升级, 当重复消费超过最大次数, 就会被发送 dead letter topic死信. Dead letter topic的命名构成是: %DLQ%${group_name}, 也是每个group一个dead letter topic. 但是和 retry topic 不一样, dead letter topic并不会主动被 group 消费, 需要单独启动group进行订阅.

  1. Dead Letter Topic 是什么时候创建
  2. Dead Letter Topic 的 queue 有多少个?
  3. rocketmq 是有 master-slave 的, slave 上也会有 dead letter topic 么?

DefaultMQPullConsumerImpl#sendMessageBack DefaultMQPushConsumerImpl#sendMessageBack

什么时候创建 dead letter topic

消费次数 > 最大消费次数 或者 delayLevel < 0 的情况下, 消息会被投递到 dead letter, 如果这个时候 dead letter 并没有被创建, 也会重新创建。

创建逻辑在 CONSUMER_SEND_MSG_BACK 协议中实现。具体调用方在 NettyRemotingClient.java 中。

    class NettyClientHandler extends SimpleChannelInboundHandler<RemotingCommand> {

        @Override
        protected void channelRead0(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception {
            processMessageReceived(ctx, msg);
        }
    }

调用链路

NettyRemotiongClient#start-> NettyRemotiongClient#NettyClientHandler ->NettyRemotingAbstract#processMessageReceived->NettyRemotingAbstract#processRequestCommand->SendMessageProcessor#asyncProcessRequest->SendMessageProcessor#asyncConsumerSendMsgBack

具体实现逻辑在 SendMessageProcessor#asyncProcessRequest

    public CompletableFuture<RemotingCommand> asyncProcessRequest(ChannelHandlerContext ctx,
                                                                  RemotingCommand request) throws RemotingCommandException {
        final SendMessageContext mqtraceContext;
        switch (request.getCode()) {
            case RequestCode.CONSUMER_SEND_MSG_BACK:
                return this.asyncConsumerSendMsgBack(ctx, request);
            default:
                SendMessageRequestHeader requestHeader = parseRequestHeader(request);
                if (requestHeader == null) {
                    return CompletableFuture.completedFuture(null);
                }
                mqtraceContext = buildMsgContext(ctx, requestHeader);
                this.executeSendMessageHookBefore(ctx, request, mqtraceContext);
                if (requestHeader.isBatch()) {
                    return this.asyncSendBatchMessage(ctx, request, mqtraceContext, requestHeader);
                } else {
                    return this.asyncSendMessage(ctx, request, mqtraceContext, requestHeader);
                }
        }
    }

然后继续查看 SendMessageProcessor#asyncConsumerSendMsgBack 方法

主要关于这段

 if (msgExt.getReconsumeTimes() >= maxReconsumeTimes
            || delayLevel < 0) {
            newTopic = MixAll.getDLQTopic(requestHeader.getGroup());
            queueIdInt = ThreadLocalRandom.current().nextInt(99999999) % DLQ_NUMS_PER_GROUP;

            topicConfig = this.brokerController.getTopicConfigManager().createTopicInSendMessageBackMethod(newTopic,
                    DLQ_NUMS_PER_GROUP,
                    PermName.PERM_WRITE | PermName.PERM_READ, 0);

            if (null == topicConfig) {
                response.setCode(ResponseCode.SYSTEM_ERROR);
                response.setRemark("topic[" + newTopic + "] not exist");
                return CompletableFuture.completedFuture(response);
            }
            msgExt.setDelayTimeLevel(0);
        } else {
            if (0 == delayLevel) {
                delayLevel = 3 + msgExt.getReconsumeTimes();
            }
            msgExt.setDelayTimeLevel(delayLevel);
        }

通过代码可以看到,当消费次数大于最大消费次数,或者 delaylevel < 0 的时候,消息会投递到 dead letter topic

通过代码可以看到 创建的 dead letter queue 个数默认为1, 因为 DLQ_NUMS_PER_GROUP 值默认为1 。

topicConfig = this.brokerController.getTopicConfigManager().createTopicInSendMessageBackMethod(newTopic,
                    DLQ_NUMS_PER_GROUP,
                    PermName.PERM_WRITE | PermName.PERM_READ, 0);

如果这个时候 dead letter 没有被创建,也会重新创建, 实现在createTopicInSendMessageBackMethod

 public TopicConfig createTopicInSendMessageBackMethod(
        final String topic,
        final int clientDefaultTopicQueueNums,
        final int perm,
        final int topicSysFlag) {
        TopicConfig topicConfig = this.topicConfigTable.get(topic);
        if (topicConfig != null)
            return topicConfig;

        boolean createNew = false;

        try {
            if (this.topicConfigTableLock.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
                try {
                    topicConfig = this.topicConfigTable.get(topic);
                    if (topicConfig != null)
                        return topicConfig;

                    topicConfig = new TopicConfig(topic);
                    topicConfig.setReadQueueNums(clientDefaultTopicQueueNums);
                    topicConfig.setWriteQueueNums(clientDefaultTopicQueueNums);
                    topicConfig.setPerm(perm);
                    topicConfig.setTopicSysFlag(topicSysFlag);

                    log.info("create new topic {}", topicConfig);
                    this.topicConfigTable.put(topic, topicConfig);
                    createNew = true;
                    this.dataVersion.nextVersion();
                    this.persist();
                } finally {
                    this.topicConfigTableLock.unlock();
                }
            }
        } catch (InterruptedException e) {
            log.error("createTopicInSendMessageBackMethod exception", e);
        }

        if (createNew) {
            this.brokerController.registerBrokerAll(false, true, true);
        }

        return topicConfig;
    }

通过代码可以看到,先判断了 topicConfig 是否为空,如果是空,重新创建。

消费失败场景下,brokerController 创建的 dead letter topic, 具体查看 this.brokerController.getTopicConfigManager().createTopicInSendMessageBackMethod

消费者客户端,可以消费 master broker, 也有可能消费 slave broker,也就是说,在消费失败的时候, dead letter 可能在 master/slave 上都会创建。

consumer 客户端消费有两种消费模式,通过代码也可以看出来:并发模式,有序模式。

processConsumeResult 会 调用checkReconsumeTimes & sendMessageBack
 private boolean checkReconsumeTimes(List<MessageExt> msgs) {
        boolean suspend = false;
        if (msgs != null && !msgs.isEmpty()) {
            for (MessageExt msg : msgs) {
                if (msg.getReconsumeTimes() >= getMaxReconsumeTimes()) {
                    MessageAccessor.setReconsumeTime(msg, String.valueOf(msg.getReconsumeTimes()));
                    if (!sendMessageBack(msg)) {
                        suspend = true;
                        msg.setReconsumeTimes(msg.getReconsumeTimes() + 1);
                    }
                } else {
                    suspend = true;
                    msg.setReconsumeTimes(msg.getReconsumeTimes() + 1);
                }
            }
        }
        return suspend;
    }

总结

  1. Dead letter topic 是什么时候创建的?

发送死信消息的时候

  1. Dead letter topic 的queue有多少个?

1个

  1. rocketmq是有master-slave的,slave上也会有dead letter topic 吗?

Master-slave 都有可能