RocketMQ之消息存储结构

    RocketMQ的消息存储是主要离不开CommitLog和ConsumerQueue这两个文件,他们相互配合完成数据的存储和读取。如下图所示:
RocketMQ之消息存储结构_第1张图片

CommitLog 消息存储物理文件

    从上图可以看出有两个非常重要的文件:CommitLog和ConsumerQueue。CommitLog是消息存储的日志数据文件,Producer 发送消息最终顺序写入到此文件中,并且CommitLog文件采用混合型存储(当前broker的所有Topic 下的消息队列共用同一个 CommitLog 的日志数据文件),并通过建立消息逻辑队列-ConsumeQueue 的方式来区分不同 Topic 下面的不同 MessageQueue 的消息,每个Topic下的每个MessageQueue都有一个对应的ConsumeQueue文件

ConsumerQueue 消息逻辑队列

    ConsumerQueue相当于字典的目录,起导索引文件的作用,用来指定消息在消息物理文件CommitLog上的位置。同时为消费消息起到一定的缓冲作用(只有 ReputMessageService 异步服务线程通过 doDispatch 异步生成了 ConsumeQueue 队列的元素后,Consumer才能进行消费)。这样,只要消息写入并刷盘至 CommitLog 文件后,消息就不会丢失,即使 ConsumeQueue 中的数据丢失,也可以通过 CommitLog 来恢复。
RocketMQ之消息存储结构_第2张图片
    ConsumeQueue中存储单元是一个20字节定长的二进制数据,顺序写顺序读。
RocketMQ之消息存储结构_第3张图片

  • CommitLog Offset:指这条消息在CommitLog文件中的实际偏移量,用此可以直接查找到CommitLog中消息
  • Size:存储消息的大小
  • Message Tag HashCode:存储消息的Tag的哈希值:主要用于订阅时消息过滤(订阅时如果指定了Tag,会根据HashCode来快速查找到订阅的消息)。用途在于,如果一个topic下有多重业务消息,那生产者可以通过设置Tag标签来进行消息的分类,消费者可以通过Tag来过滤出需要的类型消息。

ConsumeQueue存储的目录结构如下:

  • 一个Topic一个文件夹
  • 在一个Topic文件夹下,一个MessageQueue一个文件夹,使用0-n来命名
  • 一个MessageQueue文件夹里有多个文件,每个5.7m左右,用来存储属于当前MessageQueue消息的位置信息
IndexFile 消息索引文件

    ConsumerQueue是通过偏移量offset去CommitLog文件中查找消息,但实际工作应用中,我们想查找某条具体的消息,并不知道offset值,那该怎么办呢?那IndexFile作用就来了。
    IndexFile是消息索引文件,如果一个生产者发送的消息包含key值的话,会使用IndexFile存储消息索引,主要用于使用key来查询消息。文件的内容结构如图:
RocketMQ之消息存储结构_第4张图片
    在Broker端,通过Key来计算Hash槽的位置,从而找到Index索引数据。从Index索引中拿到消息的物理偏移量,最后根据这个物理偏移量,直接到CommitLog文件中去找就可以了。另外说明下,通过IndexFile来查找消息的方法不影响RocketMQ的正常生产-消费流程,它只是查询定位消息的方法而已。

Message Id 消息ID查询消息

    除了通过消息偏移量来查找消息的方式,RocketMQ还提供了其他几种方式可以查询消息。

  • 通过Message Key 查询;
  • 通过Unique Key查询;
  • 通过Message Id查询。

    在这里,Message Key和Unique Key都是在消息发送之前,由生产者发送包含Message Key 或 Unique Key的消息,那么就会给它们每一个都构建索引,也就是写入上面讲到的索引文件IndexFile中。而Message Id是在Broker端存储消息的时候生成,并返回给生产者,生产者可以把它记录下来,以便后面追溯某条消息时候直接用Message Id查询消息。
    Message Id总共 16 字节,包含消息存储主机地址和在CommitLog文件中的偏移量offset。生成Message Id方法源码如下:

/**
 * 创建消息ID
 * @param input     
 * @param addr      Broker服务器地址
 * @param offset    正在存储的消息,在Commitlog中的偏移量
 * @return
 */
public static String createMessageId(final ByteBuffer input, final ByteBuffer addr, final long offset) {
    input.flip();
    int msgIDLength = addr.limit() == 8 ? 16 : 28;
    input.limit(msgIDLength);
    input.put(addr);
    input.putLong(offset);
    return UtilAll.bytes2string(input.array());
}

当我们根据Message Id向Broker查询消息时,首先会通过一个decodeMessageId方法,将Broker地址和消息的偏移量解析出来。

public static MessageId decodeMessageId(final String msgId) throws Exception {
    SocketAddress address;
    long offset;
    int ipLength = msgId.length() == 32 ? 4 * 2 : 16 * 2;
    byte[] ip = UtilAll.string2bytes(msgId.substring(0, ipLength));
    byte[] port = UtilAll.string2bytes(msgId.substring(ipLength, ipLength + 8));
    ByteBuffer bb = ByteBuffer.wrap(port);
    int portInt = bb.getInt(0);
    //解析出来Broker地址
    address = new InetSocketAddress(InetAddress.getByAddress(ip), portInt);
    //偏移量
    byte[] data = UtilAll.string2bytes(msgId.substring(ipLength + 8, ipLength + 8 + 16));
    bb = ByteBuffer.wrap(data);
    offset = bb.getLong(0);
    return new MessageId(address, offset);
}

所以通过Message Id查询消息的时候,实际上还是直接从特定Broker上的CommitLog指定位置offset进行查询,属于精确查询。

你可能感兴趣的:(消息中间件)