RocketMQ的消息存储

RocketMQ的消息存储

RocketMQ 会创建多个MappedFile用来存储文件,每个MappedFile大小固定,有自己的内存缓冲区和对应的系统文件,所有的MappedFile由CommitLog中的MappedFileQueue统一维护

每个Broker都对应有一个MessageStore,专门用来存储发送到它的消息,不过MessageStore本身不是文件,只是存储的一个抽象,MessageStore 中保存着一个 CommitLog,CommitLog 维护了一个 MappedFileQueue,而MappedFileQueue 中又维护了多个 MappedFile,每个MappedFile都会映射到文件系统中一个文件,这些文件才是真正的存储消息的地方,MappedFile的文件名为它记录的第一条消息的全局物理偏移量

image-20200928233449538

流程图

未命名文件 (1)

1.消息发送存储流程
@Override
public PutMessageResult putMessages(MessageExtBatch messageExtBatch) {
  //如果当前 Broker 停止工作或 Broker为 SLAVE 角色或当前 Rocket 不支持写入则拒绝消息写入;如果消息主题长度超 256 个字符、消息属性长度超过 65536 个字符将拒绝该消息写人
    PutMessageStatus checkStoreStatus = this.checkStoreStatus();
    if (checkStoreStatus != PutMessageStatus.PUT_OK) {
        return new PutMessageResult(checkStoreStatus, null);
    }
	//校验topic的长度和消息的大小
    PutMessageStatus msgCheckStatus = this.checkMessages(messageExtBatch);
    if (msgCheckStatus == PutMessageStatus.MESSAGE_ILLEGAL) {
        return new PutMessageResult(msgCheckStatus, null);
    }

    long beginTime = this.getSystemClock().now();
  //把消息放进去commitLog
    PutMessageResult result = this.commitLog.putMessages(messageExtBatch);
    long elapsedTime = this.getSystemClock().now() - beginTime;
    if (elapsedTime > 500) {
        log.warn("not in lock elapsed time(ms)={}, bodyLength={}", elapsedTime, messageExtBatch.getBody().length);
    }

    this.storeStatsService.setPutMessageEntireTimeMax(elapsedTime);

    if (null == result || !result.isOk()) {
        this.storeStatsService.getPutMessageFailedTimes().incrementAndGet();
    }

    return result;
}
private PutMessageStatus checkStoreStatus() {
  //判断当前broker是否停止工作,拒绝消息写入
    if (this.shutdown) {
        log.warn("message store has shutdown, so putMessage is forbidden");
        return PutMessageStatus.SERVICE_NOT_AVAILABLE;
    }
	//如果Broker 为SLAVE 角色,拒绝消息写入
    if (BrokerRole.SLAVE == this.messageStoreConfig.getBrokerRole()) {
        long value = this.printTimes.getAndIncrement();
        if ((value % 50000) == 0) {
            log.warn("message store has shutdown, so putMessage is forbidden");
        }
        return PutMessageStatus.SERVICE_NOT_AVAILABLE;
    }
		//如果当前rocket不支持写入则拒绝消息写入
    if (!this.runningFlags.isWriteable()) {
        long value = this.printTimes.getAndIncrement();
        if ((value % 50000) == 0) {
            log.warn("message store has shutdown, so putMessage is forbidden");
        }
        return PutMessageStatus.SERVICE_NOT_AVAILABLE;
    } else {
        this.printTimes.set(0);
    }
    return PutMessageStatus.PUT_OK;
}
  1. 如果当前 Broker 停止工作或 Broker为 SLAVE 角色或当前 Rocket 不支持写入则拒绝消息写入;如果消息主题长度超 256 个字符、消息属性长度超过 65536 个字符将拒绝该消息写人
MappedFile unlockMappedFile = null;
MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile();
  1. 获取当前可以写入 Commitlog 件,Commitlog 文件存储 为${ROCKET_HOME }/ store/commitlog 录,每一个文件默认lG, 一个文件写满后再创建另外一个,以该文件中第一个偏移量为文件名,偏移量小于20 位用0补齐
    假设第一个文件初始偏移0 ,第2个文件的1073741824 ,代表该文件中的第一条消息的物理偏移 1073741824 ,这样根据物理偏移量能快速定位到消息. MappedFileQueue可以看作是{ROCKET_HOME }/store/commitlog 文件夹而MappedFile 则对应该文件夹下一个个的文件
//:在写入 CommitLog 之前,先申请 putMessageLock ,也就是将消息存储到CommitLog 文件中是串行的
putMessageLock.lock();
try {
    //设置消息的存储时间
    messageExtBatch.setStoreTimestamp(beginLockTimestamp);
//如 mappedFile为空,表明$ {ROCKET_HOME}/store/ CommitLog 目录下不存在任何文件,说明本次消息是第一次消息发送
    if (null == mappedFile || mappedFile.isFull()) {
      //用偏移量为0创建第一个commit文件,文件名0000000000000000
        mappedFile = this.mappedFileQueue.getLastMappedFile(0); // Mark: NewFile may be cause noise
    }
  //如果创建失败,抛出CREATE_MAPEDFILE_FAILED
    if (null == mappedFile) {
        log.error("Create mapped file1 error, topic: {} clientAddr: {}", messageExtBatch.getTopic(), messageExtBatch.getBornHostString());
        beginTimeInLock = 0;
        return new PutMessageResult(PutMessageStatus.CREATE_MAPEDFILE_FAILED, null);
    }
  1. 在写入 CommitLog 之前,先申请 putMessageLock ,也就是将消息存储到CommitLog 文件中是串行的.设置消息的存储时间,

如 mappedFile为空,表明$ {ROCKET_HOME}/store/ CommitLog 目录下不存在任何文件,说明本次消息是第一次消息发送,用偏移量为0创建第一个commit文件,文件名0000000000000000,如果创建失败,抛出CREATE_MAPEDFILE_FAILED

//将消息追加到mappedFile中
mappedFile.appendMessages(messageExtBatch, this.appendMessageCallback);

public AppendMessageResult appendMessagesInner(final MessageExt messageExt, final AppendMessageCallback cb) {
       //先获取mappedFile当前写指针
        int currentPos = this.wrotePosition.get();
        //如果currentPos 大于或等于文件大小则说明文件已写满,抛出 AppendMessageStatus.UNKNOWN_ ERROR 如果 currentPos 小于文件大小,通过 slice ()方法创建 一个与 MappedFile 的共存区,并设置 position 为当前指针
        if (currentPos < this.fileSize) {
          //mappedByteBuffer 即为该apped文件在内存中的映射。
            ByteBuffer byteBuffer = writeBuffer != null ? writeBuffer.slice() : this.mappedByteBuffer.slice();
            byteBuffer.position(currentPos);
            AppendMessageResult result;
            if (messageExt instanceof MessageExtBrokerInner) {
              //MappedFile的文件名 ,byteBuffer=MappedFile的内存缓冲,fileSize - currentPos=当前文件剩余容量
              //messageExt 内部封装好的消息
                result = cb.doAppend(this.getFileFromOffset(), byteBuffer, this.fileSize - currentPos, (MessageExtBrokerInner) messageExt);
            } else if (messageExt instanceof MessageExtBatch) {
                result = cb.doAppend(this.getFileFromOffset(), byteBuffer, this.fileSize - currentPos, (MessageExtBatch) messageExt);
            } else {
                return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR);
            }
            this.wrotePosition.addAndGet(result.getWroteBytes());
            this.storeTimestamp = result.getStoreTimestamp();
            return result;
        }
        log.error("MappedFile.appendMessage return null, wrotePosition: {} fileSize: {}", currentPos, this.fileSize);
        return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR);
    }
  1. 将消息追加到 MappedFile 首先先获取 MappedFile 当前写指针,如果currentPos 大于或等于文件大小则 明文件已写满,抛出 AppendMessageStatus. UNKNOWN_ ERROR, 如果 currentPos 小于文件大小,通过 slice ()方法创建 个与 MappedFile 的共

    存区,并设置 position 为当前指针,mappedByteBuffer 即为该文件在内存中的映射。当追加消息到MappedFile中,会优先追加到 writeBuffer中

// :创建全局唯一消息 ID ,消息 ID 有16 字节
long wroteOffset = fileFromOffset + byteBuffer.position();
this.resetByteBuffer(storeHostHolder, storeHostLength);
String msgId = MessageDecoder.createMessageId(this.msgIdMemory, msgInner.getStoreHostBytes(storeHostHolder), wroteOffset);
//获取该消息在消息队列的偏移量,CommitLog 中保存了当前所有消息队列的当前待写入偏移量
keyBuilder.setLength(0);
keyBuilder.append(msgInner.getTopic());
keyBuilder.append('-');
keyBuilder.append(msgInner.getQueueId());
String key = keyBuilder.toString();
Long queueOffset = CommitLog.this.topicQueueTable.get(key);
if (null == queueOffset) {
    queueOffset = 0L;
    CommitLog.this.topicQueueTable.put(key, queueOffset);
}
//:如果消息长度+END FILE_ MIN_ BLANK_ LENGTH 大于 CommitLog 文件的空闲空间,则返回 AppendMessageStatus.END_OF _FILE, Broker 会重新创建一个新的CommitLog 文件来存储该消息 从这里可以看出,每个 CommitLog 文件最少会空 个字节,高 字节存储当前文件剩余空间,低 字节存储魔数 CommitLog.BLANK MAGIC CODE
final int msgLen = calMsgLength(msgInner.getSysFlag(), bodyLength, topicLength, propertiesLength);
  1. 创建全局唯一消息 ID .获取该消息在消息队列的偏移量,CommitLog 中保存了当前所有消息队列的当前待写入偏移量
final long beginTimeMills = CommitLog.this.defaultMessageStore.now();
// Write messages to the queue buffer
byteBuffer.put(this.msgStoreItemMemory.array(), 0, msgLen);

AppendMessageResult result = new AppendMessageResult(AppendMessageStatus.PUT_OK, wroteOffset, msgLen, msgId,
    msgInner.getStoreTimestamp(), queueOffset, CommitLog.this.defaultMessageStore.now() - beginTimeMills);
  1. 返回追加成功的结果,将消息内容存储到 ByteBuffer 中,然后创建 AppendMessageResult 这里只是将消息存储在 MappedFile 应的内存映射 buffer ,并没有刷写到磁盘
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值