面试官:聊聊rocketmq生产者msgId为什么会与consumer的msgId为什么对不上

884 阅读3分钟

这里是weihubeats,觉得文章不错可以关注公众号小奏技术,文章首发。拒绝营销号,拒绝标题党

rocketmq版本

  • 5.1.0

现象

我们使用官方的发送消息和消费消息demo运行得到如下结果

消息发送

        DefaultMQProducer producer = new DefaultMQProducer(PRODUCER_GROUP);

        producer.setNamesrvAddr(DEFAULT_NAMESRVADDR);
        producer.start();
        Message msg = new Message(TOPIC /* Topic */,
                    TAG /* Tag */,
                    ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
                );
                SendResult sendResult = producer.send(msg);
                DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
                System.out.printf("%s %s%n", sendResult, dtf2.format(LocalDateTime.now()));

打印的结果如下:

可以看到几个重要信息

  • msgId: 0A0A003A4833251A69D78E7563400000
  • offsetMsgId: AC1963C4000078BF000000001D4E6C2A

消息消费

        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(CONSUMER_GROUP);
        consumer.setNamesrvAddr(DEFAULT_NAMESRVADDR);
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        consumer.subscribe(TOPIC, "*");
        consumer.registerMessageListener((MessageListenerConcurrently) (msg, context) -> {
            System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msg);
//            return ConsumeConcurrentlyStatus.RECONSUME_LATER;
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        });
        consumer.start();

打印结果如下:

我们也是看看几个重要信息

  • msgId:AC1963C4000078BF000000001D4E6C2A
  • UNIQ_KEY: 0A0A003A4833251A69D78E7563400000

疑问

这里我们发现了一个问题: 生产者发送的消息中的msgId在消费者中变成了UNIQ_KEY 生产者中的offsetMsgId才是消费者中的msgId

为什么好好的msgId在消费者中变成了UNIQ_KEY 带着这个疑问我们去看看消费者的msgId是如何获取的

注意这里看到的结果都是通过调用toString()方法打印的

先说msgIdoffsetMsgId是什么

  • msgId: 这个id是由客户端发送消息的时候生成,全局唯一,又叫UNIQ_KEY
  • offsetMsgId:消息偏移ID,该 ID 记录了消息所在集群的物理地址,主要包含所存储 Broker 服务器的地址( IP 与端口号)以及所在commitlog 文件的物理偏移量(采用”IP地址+Port端口”与“CommitLog的物理偏移量地址”做了一个字符串拼接),在consumer通过调用toString打印的msgId即为offsetMsgId

消费者msgId获取源码分析

我们首先看看producermsgId是怎么来的 通过查看SendResult的构造方法可以看到

这里构造的SendResult中的

  • msgId = uniqMsgId
  • offsetMsgId = `responseHeader.getMsgId()``

这里看好像msgId就是UNIQ_KEY 我们再看看msgId的获取来确认一下

public static final String PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX = "UNIQ_KEY";
    String uniqMsgId = MessageClientIDSetter.getUniqID(msg);
    return msg.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX);

可以很清晰的看到product中的msgId就是uniqMsgId

我们再看看consumer中的UNIQ_KEY是如何获取到的 我们跟踪源码可以看到有如下代码

这里就可以看到UNIQ_KEYclient在消息发送生成的

uniq key生成规则

生成规则我们可以简单看看

public static String createUniqID() {
        char[] sb = new char[LEN * 2];
        System.arraycopy(FIX_STRING, 0, sb, 0, FIX_STRING.length);
        long current = System.currentTimeMillis();
        if (current >= nextStartTime) {
            setStartTime(current);
        }
        int diff = (int)(current - startTime);
        if (diff < 0 && diff > -1000_000) {
            // may cause by NTP
            diff = 0;
        }
        int pos = FIX_STRING.length;
        UtilAll.writeInt(sb, pos, diff);
        pos += 8;
        UtilAll.writeShort(sb, pos, COUNTER.getAndIncrement());
        return new String(sb);
    }

用到的FIX_STRING主要是在静态代码块中运行的,我们可以看看

    static {
        byte[] ip;
        try {
            ip = UtilAll.getIP();
        } catch (Exception e) {
            ip = createFakeIP();
        }
        LEN = ip.length + 2 + 4 + 4 + 2;
        ByteBuffer tempBuffer = ByteBuffer.allocate(ip.length + 2 + 4);
        tempBuffer.put(ip);
        tempBuffer.putShort((short) UtilAll.getPid());
        tempBuffer.putInt(MessageClientIDSetter.class.getClassLoader().hashCode());
        FIX_STRING = UtilAll.bytes2string(tempBuffer.array()).toCharArray();
        setStartTime(System.currentTimeMillis());
        COUNTER = new AtomicInteger(0);
    }

这段代码有生成规则是如下:

  1. 总长度为20
  2. 生成策略由客户端的IP、进程ID、加载 MessageClientIDSetter 的类加载器的 hashcode结合

解惑

所以我们这里知道了product中的SendResult返回的msgId实际是uniqID

至于consumer中的msgId很明显就是offsetMsgId

dashboard疑惑

实际我们发现在rocketmq-dashboard无论是使用msgId还是offsetMsgId好像我们都能查询到我们想要的结果,这是为什么呢 如果我们查看源码会发现rocketmq提供的MQAdmin工具类中帮我们做了兼容处理

可以我们先会使用offsetMsgId去查询,如果报错则try catch这个异常,然后用queryMessageByUniqKey去查询

由于在底层屏蔽了这个差异,导致我们很多人会分不清UniqKeyoffsetMsgId

总结

  1. 由于我们在使用rocketmq-dashboard查询的时候底层帮我们屏蔽了msgIdoffsetMsgId,导致我们不会去细分二则的区别
  2. consumer调用toString方法打印出来的msgId实际是offsetMsgId,所以会与product中的msgId对不上,因为product中的msgId实际是UniqKey