RocketMQ-消费者重试原理

源码版本号:版本号:4.9.4

以并发消费ConsumeMessageConcurrentlyService为例

需要实现MessageListenerConcurrentlyconsumeMessage方法,该方法返回的是枚举值ConsumeConcurrentlyStatus

public enum ConsumeConcurrentlyStatus {
    /**
     * 消费成功
     */
    CONSUME_SUCCESS,
    /**
     * 消费失败, 延迟一段时间后会继续消费
     */
    RECONSUME_LATER;
}

查看ConsumeMessageConcurrentlyService中调用
MessageListenerConcurrently#consumeMessage方法

public class ConsumeMessageConcurrentlyService implements ConsumeMessageService {
    class ConsumeRequest implements Runnable {
        @Override
        public void run() {
            try {
                // 省略......
                /**
                 * 402行 
                 * 调用MessageListenerConcurrently#consumeMessage方法
                 */
                status = listener.consumeMessage(Collections.unmodifiableList(msgs), context);
            } catch (Throwable e) {
                // 省略......
            }
            // 省略
            /**
             * 430行
             * 如果消费时出现异常或者消费后本来就是返回了null
             * 则将状态修改为ConsumeConcurrentlyStatus.RECONSUME_LATER
             * 代表稍后还会再消费
             */
            if (null == status) {
                status = ConsumeConcurrentlyStatus.RECONSUME_LATER;
            }
            // 省略......
            if (!processQueue.isDropped()) {
                // 448行 最后会根据消费状态status做一些处理
                ConsumeMessageConcurrentlyService.this.processConsumeResult(status, context, this);
            } else {
                log.warn("processQueue is dropped without process consume result. messageQueue={}, msgs={}", messageQueue, msgs);
            }
        }
    }
}

接着看ConsumeMessageConcurrentlyService#processConsumeResult方法

public class ConsumeMessageConcurrentlyService implements ConsumeMessageService {
    // 241行
    public void processConsumeResult(
            final ConsumeConcurrentlyStatus status,
            final ConsumeConcurrentlyContext context,
            final ConsumeRequest consumeRequest
    ) {
        // 这里会返回 Integer.MAX_VALUE
        int ackIndex = context.getAckIndex();
        switch (status) {
            case CONSUME_SUCCESS:
                if (ackIndex >= consumeRequest.getMsgs().size()) {
                    ackIndex = consumeRequest.getMsgs().size() - 1;
                }
                // 省略
                break;
            case RECONSUME_LATER:
                ackIndex = -1;
                // 省略
                break;
            default:
                break;
        }
        // 省略......
        // 270行
        switch (this.defaultMQPushConsumer.getMessageModel()) {
            // 广播模式
            case BROADCASTING:
                // 只有消费失败即ackIndex为-1才会运行下面的for循环
                for (int i = ackIndex + 1; i < consumeRequest.getMsgs().size(); i++) {
                    MessageExt msg = consumeRequest.getMsgs().get(i);
                    log.warn("BROADCASTING, the message consume failed, drop it, {}", msg.toString());
                }
                break;
            // 集群模式    
            case CLUSTERING:
                List<MessageExt> msgBackFailed = new ArrayList<MessageExt>(consumeRequest.getMsgs().size());
                // 只有消费失败即ackIndex为-1才会运行下面的for循环
                for (int i = ackIndex + 1; i < consumeRequest.getMsgs().size(); i++) {
                    MessageExt msg = consumeRequest.getMsgs().get(i);
                    // 这里会将消息投递到重试topic:%RETRY%+consumerGroup
                    boolean result = this.sendMessageBack(msg, context);
                    if (!result) {
                        msg.setReconsumeTimes(msg.getReconsumeTimes() + 1);
                        msgBackFailed.add(msg);
                    }
                }

                if (!msgBackFailed.isEmpty()) {
                    consumeRequest.getMsgs().removeAll(msgBackFailed);
                    // 如果发送重试消息失败了, 则会在本地延迟一段时间后继续提交消费
                    this.submitConsumeRequestLater(msgBackFailed, consumeRequest.getProcessQueue(), consumeRequest.getMessageQueue());
                }
                break;
            default:
                break;
        }
        /**
         * 298行
         * 从未消费消息列表里面拿到消息的最小offset
         */
        long offset = consumeRequest.getProcessQueue().removeMessage(consumeRequest.getMsgs());
        if (offset >= 0 && !consumeRequest.getProcessQueue().isDropped()) {
            // 更新消费进度
            this.defaultMQPushConsumerImpl.getOffsetStore().updateOffset(consumeRequest.getMessageQueue(), offset, true);
        }
    }
}

如果消费状态为CONSUME_SUCCESS,那么ackIndex为消费消息数量减1;
如果消费状态为RECONSUME_LATER,那么ackIndex为-1。

如果消费失败,对于广播模式,只是打印warn日志。
对于集群模式,会先尝试将这个消息投递到重试topic=%RETRY%+consumerGroup
如果发送重试队列失败,则会在本地延迟一段时间后继续提交消费。

sendMessageBack方法主要是向broker发送消息,代码逻辑比较简单。

sendMessageBack会往broker发送RequestCode
CONSUMER_SEND_MSG_BACK的请求,处理的入口在这里:这里是broker源码的逻辑

/**
 * 子项目: broker
 * 包名: org.apache.rocketmq.broker.processor;
 */
public class SendMessageProcessor extends AbstractSendMessageProcessor implements NettyRequestProcessor {
    /**
     * 90行
     */
    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:
                // 省略
        }
    }
    // 117行
    private CompletableFuture<RemotingCommand> asyncConsumerSendMsgBack(ChannelHandlerContext ctx,
                                                                        RemotingCommand request) throws RemotingCommandException {
        // 147行, 生成新的topic=%RETRY+consumerGroup
        String newTopic = MixAll.getRetryTopic(requestHeader.getGroup());
        // 192行
        if (msgExt.getReconsumeTimes() >= maxReconsumeTimes
                || delayLevel < 0) {
            /**
             * 消息的重试最多进行 16 次,如果超过了 16 次就会将其丢进死信队列当中了
             */
            newTopic = MixAll.getDLQTopic(requestHeader.getGroup());
        } else {
            /**
             * 208行
             * 这里值得注意的是,这个delayLevel就是消费者传过来的参数,并且它一直都是 0。
             * 所以,基于上面的代码,delayLevel的值完全取决于当前Message的重试次数,
             * 即 ReconsumeTimes。
             */
            if (0 == delayLevel) {
                delayLevel = 3 + msgExt.getReconsumeTimes();
            }
            msgExt.setDelayTimeLevel(delayLevel);
        }
        // 最后将消息寻到延时队列里面
    }
    
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值