• rocketMQ retry 消息的实现


    consumer 消费失败,会把消息重新发往 %RETRY% + consumerGroup,这个 retry 消息会在一定时间后,真实送到 retry topic。

    broker 处理发送到 retry topic 的消息:

    org.apache.rocketmq.broker.processor.SendMessageProcessor#consumerSendMsgBack

    消息消费超过最大次数或者客户端配置了直接发送到死信队列,则把消息发送到死信队列,否则把消息发送 retry topic,虽然看起来是把消息直接写入 %RETRY% + consumerGroup

    但其实在 putMessage 的时候,会把消息写入 SCHEDULE_TOPIC_XXXX

    // org.apache.rocketmq.store.CommitLog#putMessage
    if (msg.getDelayTimeLevel() > 0) {
        if (msg.getDelayTimeLevel() > this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel()) {
            msg.setDelayTimeLevel(this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel());
        }
    
        topic = ScheduleMessageService.SCHEDULE_TOPIC;
        queueId = ScheduleMessageService.delayLevel2QueueId(msg.getDelayTimeLevel());
    
        // Backup real topic, queueId
        MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_TOPIC, msg.getTopic());
        MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_QUEUE_ID, String.valueOf(msg.getQueueId()));
        msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties()));
    
        msg.setTopic(topic);
        msg.setQueueId(queueId);
    }

    SCHEDULE_TOPIC_XXXX 这个 topic 非常有意思,broker 并没有显式创建这个 topic,即 nameserver 和 broker 没有保存这个 broker 的元数据,topic 的数据会正常写入 commitLog,一个 delay 等级对应一个 queue,queueId = delayLevel - 1,所以 SCHEDULE_TOPIC_XXXX 最多有 18 个 queue。

    // org.apache.rocketmq.store.config.MessageStoreConfig#messageDelayLevel
    private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";

    一共有 18 个 delayLevel

    // org.apache.rocketmq.common.subscription.SubscriptionGroupConfig#retryMaxTimes
    private int retryMaxTimes = 16;

    这个参数 consumer 不可配置,默认 16

    ScheduleMessageService 初始化 delayLevelTable,键是 delayLevel,值是 delay 的毫秒数,从 1 到 18

    // org.apache.rocketmq.store.schedule.ScheduleMessageService#load
    public boolean load() {
        boolean result = super.load();
        result = result && this.parseDelayLevel();
        return result;
    }
    public boolean parseDelayLevel() {
        HashMap<String, Long> timeUnitTable = new HashMap<String, Long>();
        timeUnitTable.put("s", 1000L);
        timeUnitTable.put("m", 1000L * 60);
        timeUnitTable.put("h", 1000L * 60 * 60);
        timeUnitTable.put("d", 1000L * 60 * 60 * 24);
    
        String levelString = this.defaultMessageStore.getMessageStoreConfig().getMessageDelayLevel();
        try {
            String[] levelArray = levelString.split(" ");
            for (int i = 0; i < levelArray.length; i++) {
                String value = levelArray[i];
                String ch = value.substring(value.length() - 1);
                Long tu = timeUnitTable.get(ch);
    
                int level = i + 1;
                if (level > this.maxDelayLevel) {
                    this.maxDelayLevel = level;
                }
                long num = Long.parseLong(value.substring(0, value.length() - 1));
                long delayTimeMillis = tu * num;
                this.delayLevelTable.put(level, delayTimeMillis);
            }
        } catch (Exception e) {
            log.error("parseDelayLevel exception", e);
            log.info("levelString String = {}", levelString);
            return false;
        }
    
        return true;
    }

    ScheduleMessageService 针对每一个 level 创建一个定时任务,遍历 consume queue,判断消息是否到期,到期则把消息写入真实 topic

    // org.apache.rocketmq.store.schedule.ScheduleMessageService#start
    public void start() {
        for (Map.Entry<Integer, Long> entry : this.delayLevelTable.entrySet()) {
            Integer level = entry.getKey();
            Long timeDelay = entry.getValue();
            Long offset = this.offsetTable.get(level);
            if (null == offset) {
                offset = 0L;
            }
    
            if (timeDelay != null) {
                this.timer.schedule(new DeliverDelayedMessageTimerTask(level, offset), FIRST_DELAY_TIME);
            }
        }
    
        // 定期持久化处理完的 queue offset 到 delayOffset.json 文件中
        this.timer.scheduleAtFixedRate(new TimerTask() {
    
            @Override
            public void run() {
                try {
                    ScheduleMessageService.this.persist();
                } catch (Throwable e) {
                    log.error("scheduleAtFixedRate flush exception", e);
                }
            }
        }, 10000, this.defaultMessageStore.getMessageStoreConfig().getFlushDelayOffsetInterval());
    }
  • 相关阅读:
    玩转MySQL之Linux下的简单操作(服务启动与关闭、启动与关闭、查看版本)
    玩转MySQL之Linux下修改默认编码
    机器学习算法及应用领域相关的中国大牛
    [转载]Python 包管理工具解惑
    Vim常用操作和快捷键技巧总结
    [转载]那些C++牛人的博客
    [转载]学习c/c++的好网站
    [转载]C++内存管理
    [转载]SQL数据库如何加快查询速度
    [转载]Python3.x和Python2.x的区别
  • 原文地址:https://www.cnblogs.com/allenwas3/p/11922650.html
Copyright © 2020-2023  润新知