第十一章-Broker-消息存储(一)

书接第九章的消息处理,最终都需要将消息存储起来,从源码的角度,针对单个消息和批量消息,分别续接章节9.1章节9.2的实现,代码实现如下:

单个消息发送

putMessageResult = this.brokerController.getMessageStore().putMessage(msgInner);

批量消息发送

putMessageResult = this.brokerController.getMessageStore().putMessages(messageExtBatch);

两者实现源码都在this.brokerController.getMessageStore()这个类中,由上下午可知,该类为DefaultMessageStore,单个和批量的处理也容易识别,分别是putMessage和putMessages(多个则加s),入口找到之后,接下来,我们继续顺着这个源码讲下去。

11.1 单个消息存储

public PutMessageResult putMessage(MessageExtBrokerInner msg) {
    if (this.shutdown) { // 状态判断
        log.warn("message store has shutdown, so putMessage is forbidden");
        return new PutMessageResult(PutMessageStatus.SERVICE_NOT_AVAILABLE, null);
    }
    // 从节点判断,从节点不可接收生产者消息的处理和存储,所以返回服务不可用
    if (BrokerRole.SLAVE == this.messageStoreConfig.getBrokerRole()) {
        long value = this.printTimes.getAndIncrement();
        if ((value % 50000) == 0) {
            log.warn("message store is slave mode, so putMessage is forbidden ");
        }

        return new PutMessageResult(PutMessageStatus.SERVICE_NOT_AVAILABLE, null);
    }
    // 判断是否可写,不可写,也返回服务不可用
    if (!this.runningFlags.isWriteable()) {
        long value = this.printTimes.getAndIncrement();
        if ((value % 50000) == 0) {
            log.warn("message store is not writeable, so putMessage is forbidden " + this.runningFlags.getFlagBits());
        }

        return new PutMessageResult(PutMessageStatus.SERVICE_NOT_AVAILABLE, null);
    } else {
        this.printTimes.set(0);
    }
    // topic 长度不能大于 127
    if (msg.getTopic().length() > Byte.MAX_VALUE) {
        log.warn("putMessage message topic length too long " + msg.getTopic().length());
        return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, null);
    }
    // 属性长度不能大于 32767
    if (msg.getPropertiesString() != null && msg.getPropertiesString().length() > Short.MAX_VALUE) {
        log.warn("putMessage message properties length too long " + msg.getPropertiesString().length());
        return new PutMessageResult(PutMessageStatus.PROPERTIES_SIZE_EXCEEDED, null);
    }
    // 判断操作系统 page cache 忙碌,这里是通过当前时间与正在操作的线程之间的操作时差来判断的,在一定范围内(1000,10000000)毫秒,就是忙碌,正常情况,每次操作完成,时间会置0,那么时差就不会在规定的范围内,这个后面也会通过源码来讲
    if (this.isOSPageCacheBusy()) {
        return new PutMessageResult(PutMessageStatus.OS_PAGECACHE_BUSY, null);
    }
	// 记录开始存储时间
    long beginTime = this.getSystemClock().now();
    // 存储消息,看`章节11.3`
    PutMessageResult result = this.commitLog.putMessage(msg);
    // 记录存储所花时间
    long eclipseTime = this.getSystemClock().now() - beginTime;
    if (eclipseTime > 500) { // 针对超过500毫秒的操作,记录日志,因为正常这个操作很快的,不会超过这个时间
        log.warn("putMessage not in lock eclipse time(ms)={}, bodyLength={}", eclipseTime, msg.getBody().length);
    }
    // 每次记录 Broker 全局存储消息所花时间段的次数统计,比如10毫秒以内的发生了多少次、50毫秒以内的多少次,等等
    this.storeStatsService.setPutMessageEntireTimeMax(eclipseTime);
    // 存储失败,记录失败次数+1
    if (null == result || !result.isOk()) {
        this.storeStatsService.getPutMessageFailedTimes().incrementAndGet();
    }
    // 返回结果
    return result;
}

11.2 批量消息存储

public PutMessageResult putMessages(MessageExtBatch messageExtBatch) {
    if (this.shutdown) { // 状态判断
        log.warn("DefaultMessageStore has shutdown, so putMessages is forbidden");
        return new PutMessageResult(PutMessageStatus.SERVICE_NOT_AVAILABLE, null);
    }
    // 从节点判断,从节点不可接收生产者消息的处理和存储,所以返回服务不可用
    if (BrokerRole.SLAVE == this.messageStoreConfig.getBrokerRole()) {
        long value = this.printTimes.getAndIncrement();
        if ((value % 50000) == 0) {
            log.warn("DefaultMessageStore is in slave mode, so putMessages is forbidden ");
        }

        return new PutMessageResult(PutMessageStatus.SERVICE_NOT_AVAILABLE, null);
    }
    // 判断是否可写,不可写,也返回服务不可用
    if (!this.runningFlags.isWriteable()) {
        long value = this.printTimes.getAndIncrement();
        if ((value % 50000) == 0) {
            log.warn("DefaultMessageStore is not writable, so putMessages is forbidden " + this.runningFlags.getFlagBits());
        }

        return new PutMessageResult(PutMessageStatus.SERVICE_NOT_AVAILABLE, null);
    } else {
        this.printTimes.set(0);
    }
    // topic 长度不能大于 127
    if (messageExtBatch.getTopic().length() > Byte.MAX_VALUE) {
        log.warn("PutMessages topic length too long " + messageExtBatch.getTopic().length());
        return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, null);
    }
    // 批量消息总长度不能大于 4M
    if (messageExtBatch.getBody().length > messageStoreConfig.getMaxMessageSize()) {
        log.warn("PutMessages body length too long " + messageExtBatch.getBody().length);
        return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, null);
    }
	// 判断操作系统 page cache 忙碌,这里是通过当前时间与正在操作的线程之间的操作时差来判断的,在一定范围内(1000,10000000)毫秒,就是忙碌,正常情况,每次操作完成,时间会置0,那么时差就不会在规定的范围内,这个后面也会通过源码来讲
    if (this.isOSPageCacheBusy()) {
        return new PutMessageResult(PutMessageStatus.OS_PAGECACHE_BUSY, null);
    }
	// 记录开始存储时间
    long beginTime = this.getSystemClock().now();
    PutMessageResult result = this.commitLog.putMessages(messageExtBatch);
	// 记录存储所花时间
    long eclipseTime = this.getSystemClock().now() - beginTime;
    if (eclipseTime > 500) {// 针对超过500毫秒的操作,记录日志,因为正常这个操作很快的,不会超过这个时间
        log.warn("not in lock eclipse time(ms)={}, bodyLength={}", eclipseTime, messageExtBatch.getBody().length);
    }
     // 每次记录 Broker 全局存储消息所花时间段的次数统计,比如10毫秒以内的发生了多少次、50毫秒以内的多少次,等等
    this.storeStatsService.setPutMessageEntireTimeMax(eclipseTime);
  	// 存储失败,记录失败次数+1
    if (null == result || !result.isOk()) {
        this.storeStatsService.getPutMessageFailedTimes().incrementAndGet();
    }
	// 返回结果
    return result;
}

11.3 CommitLog

11.3.1 putMessage

public PutMessageResult putMessage(final MessageExtBrokerInner msg) {
    // 设置存储时间
    msg.setStoreTimestamp(System.currentTimeMillis());
    // 设置消息体 body的CRC,对消息的合法性校验
    msg.setBodyCRC(UtilAll.crc32(msg.getBody()));
    // 返回结果对象
    AppendMessageResult result = null;
    // 存储统计服务,做一些统计数据的收集工作
    StoreStatsService storeStatsService = this.defaultMessageStore.getStoreStatsService();

    String topic = msg.getTopic(); // topic
    int queueId = msg.getQueueId(); // 对应的 cq id
    // 事务类型,这里先忽略事务,通篇讲解都是忽略事务消息的处理的
    final int tranType = MessageSysFlag.getTransactionValue(msg.getSysFlag());
    if (tranType == MessageSysFlag.TRANSACTION_NOT_TYPE // 普通非事务消息类型
        || tranType == MessageSysFlag.TRANSACTION_COMMIT_TYPE) {
        // 针对延迟消息的的处理
        if (msg.getDelayTimeLevel() > 0) { // 延迟级别必须要大于0
            // 延迟等级有:1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
            if (msg.getDelayTimeLevel() > this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel()) {
                msg.setDelayTimeLevel(this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel());
            }
		   // 延迟消息的topic要换成:SCHEDULE_TOPIC_XXXX,由Broker统一管理
            topic = ScheduleMessageService.SCHEDULE_TOPIC;
            // queueId 等于延迟级别 -1 
            queueId = ScheduleMessageService.delayLevel2QueueId(msg.getDelayTimeLevel());

            // 备份真实的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()));

            // 更新为延迟消息的topic和queueId
            msg.setTopic(topic);
            msg.setQueueId(queueId);
        }
    }

    long eclipseTimeInLock = 0;
    MappedFile unlockMappedFile = null;
    // 拿到最新可写的 commitlog 文件,并通过mmap映射进内存读写,关于对 commitlog 文件的管理,请看`章节11.3.3`
    MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile();
    // 加锁处理,自旋锁或可重入锁来实现锁功能,具体用哪种实现,可配置
    putMessageLock.lock(); //spin or ReentrantLock ,depending on store config
    try {
        // 记录上锁时间
        long beginLockTimestamp = this.defaultMessageStore.getSystemClock().now();
        // 开始时间,前两节中,判断 page cache是否忙碌 isOSPageCacheBusy,就是通过这个时间来判断的,该时间在处理完后,会置0
        this.beginTimeInLock = beginLockTimestamp;

        // 设置消息存储时间
        msg.setStoreTimestamp(beginLockTimestamp);
        // commitlog 映射文件不存在,或者满了
        if (null == mappedFile || mappedFile.isFull()) {
            // 重新创建一个新的文件来映射处理,具体细节看`章节11.3.3`
            mappedFile = this.mappedFileQueue.getLastMappedFile(0); // Mark: NewFile may be cause noise
        }
        // 最终还是为空,那直接返回失败结果
        if (null == mappedFile) {
            log.error("create mapped file1 error, topic: " + msg.getTopic() + " clientAddr: " + msg.getBornHostString());
            beginTimeInLock = 0;
            return new PutMessageResult(PutMessageStatus.CREATE_MAPEDFILE_FAILED, null);
        }
	    // 往commitlog文件中写入消息,看`章节11.3.4`
        result = mappedFile.appendMessage(msg, this.appendMessageCallback);
        switch (result.getStatus()) { // 写入结果
            case PUT_OK:
                break; // 写入成功,直接退出 switch
            case END_OF_FILE: // 当前文件剩余空间不足以写入消息
                unlockMappedFile = mappedFile;
                // 新建文件,重写消息,具体细节看`章节11.3.3`
                mappedFile = this.mappedFileQueue.getLastMappedFile(0);
                if (null == mappedFile) {  // 最终还是为空,那直接返回失败结果
                    // XXX: warn and notify me
                    log.error("create mapped file2 error, topic: " + msg.getTopic() + " clientAddr: " + msg.getBornHostString());
                    beginTimeInLock = 0;
                    return new PutMessageResult(PutMessageStatus.CREATE_MAPEDFILE_FAILED, result);
                }
                // 往commitlog文件中写入消息,看`章节11.3.4`
                result = mappedFile.appendMessage(msg, this.appendMessageCallback);
                break;
            case MESSAGE_SIZE_EXCEEDED:
            case PROPERTIES_SIZE_EXCEEDED: // 消息或属性长度超了,直接返回非法消息的结果
                beginTimeInLock = 0;
                return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, result);
            case UNKNOWN_ERROR: // 未知错误
                beginTimeInLock = 0;
                return new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, result);
            default: // 默认,按未知错误处理
                beginTimeInLock = 0;
                return new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, result);
        }
	    // 记录所花时间
        eclipseTimeInLock = this.defaultMessageStore.getSystemClock().now() - beginLockTimestamp;
        // beginTimeInLock 值置0
        beginTimeInLock = 0;
    } finally {
        // 释放锁
        putMessageLock.unlock();
    }
    // 所花时间超过500,就要日志记录下,如果出现很多这种日志,程序员/运维工程师就得警惕起来了,要排查问题
    if (eclipseTimeInLock > 500) {
        log.warn("[NOTIFYME]putMessage in lock cost time(ms)={}, bodyLength={} AppendMessageResult={}", eclipseTimeInLock, msg.getBody().length, result);
    }
    // 解锁映射文件
    if (null != unlockMappedFile && this.defaultMessageStore.getMessageStoreConfig().isWarmMapedFileEnable()) {
        this.defaultMessageStore.unlockMappedFile(unlockMappedFile);
    }
	// 能走到这里,证明存储成功了,设置返回成功结果
    PutMessageResult putMessageResult = new PutMessageResult(PutMessageStatus.PUT_OK, result);

    // 统计:增加该topic存储成功次数+1
    storeStatsService.getSinglePutMessageTopicTimesTotal(msg.getTopic()).incrementAndGet();
    // 统计:增加该topic存储成功消息字节+写入字节的大小
    storeStatsService.getSinglePutMessageTopicSizeTotal(topic).addAndGet(result.getWroteBytes());
	// 刷盘,看`章节12.1`
    handleDiskFlush(result, putMessageResult, msg);
    // 复制到从节点备份,看`章节12.2`
    handleHA(result, putMessageResult, msg);
	// 返回结果
    return putMessageResult;
}

11.3.2 putMessages

public PutMessageResult putMessages(final MessageExtBatch messageExtBatch) {
    // 设置存储时间
    messageExtBatch.setStoreTimestamp(System.currentTimeMillis());
    // 返回结果对象
    AppendMessageResult result;
    // 存储统计服务,做一些统计数据的收集工作
    StoreStatsService storeStatsService = this.defaultMessageStore.getStoreStatsService();
    // 事务类型,这里先忽略事务,通篇讲解都是忽略事务消息的处理的
    final int tranType = MessageSysFlag.getTransactionValue(messageExtBatch.getSysFlag());
	// 批量消息只能是普通消息,不能是事务消息,如果违法该规则,直接返回消息非法
    if (tranType != MessageSysFlag.TRANSACTION_NOT_TYPE) {
        return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, null);
    }
    // 批量消息也不能是延迟消息,如果违法该规则,直接返回消息非法
    if (messageExtBatch.getDelayTimeLevel() > 0) {
        return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, null);
    }

    long eclipseTimeInLock = 0;
    MappedFile unlockMappedFile = null;
     // 拿到最新可写的 commitlog 文件,并通过mmap映射进内存读写,关于对 commitlog 文件的管理,请看`章节11.3.3`
    MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile();

    //fine-grained lock instead of the coarse-grained
    MessageExtBatchEncoder batchEncoder = batchEncoderThreadLocal.get();

    messageExtBatch.setEncodedBuff(batchEncoder.encode(messageExtBatch));
    // 加锁处理,自旋锁或可重入锁来实现锁功能,具体用哪种实现,可配置
    putMessageLock.lock();
    try {
        // 记录上锁时间
        long beginLockTimestamp = this.defaultMessageStore.getSystemClock().now();
        // 开始时间,前两节中,判断 page cache是否忙碌 isOSPageCacheBusy,就是通过这个时间来判断的,该时间在处理完后,会置0
        this.beginTimeInLock = beginLockTimestamp;

        // 设置消息存储时间
        messageExtBatch.setStoreTimestamp(beginLockTimestamp);
		// commitlog 映射文件不存在,或者满了
        if (null == mappedFile || mappedFile.isFull()) {
             // 重新创建一个新的文件来映射处理,具体细节看`章节11.3.3`
            mappedFile = this.mappedFileQueue.getLastMappedFile(0); // Mark: NewFile may be cause noise
        }
        // 最终还是为空,那直接返回失败结果
        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);
        }
	    // 往commitlog文件中写入消息,看`章节11.3.4`
        result = mappedFile.appendMessages(messageExtBatch, this.appendMessageCallback);
        switch (result.getStatus()) {
            case PUT_OK:
                break; // 写入成功,直接退出 switch
            case END_OF_FILE: // 当前文件剩余空间不足以写入消息
                unlockMappedFile = mappedFile;
                 // 新建文件,重写消息,具体细节看`章节11.3.3`
                mappedFile = this.mappedFileQueue.getLastMappedFile(0);
                if (null == mappedFile) { // 最终还是为空,那直接返回失败结果
                    // XXX: warn and notify me
                    log.error("Create mapped file2 error, topic: {} clientAddr: {}", messageExtBatch.getTopic(), messageExtBatch.getBornHostString());
                    beginTimeInLock = 0;
                    return new PutMessageResult(PutMessageStatus.CREATE_MAPEDFILE_FAILED, result);
                }
                 // 往commitlog文件中写入消息,看`章节11.3.4`
                result = mappedFile.appendMessages(messageExtBatch, this.appendMessageCallback);
                break;
            case MESSAGE_SIZE_EXCEEDED:
            case PROPERTIES_SIZE_EXCEEDED: // 消息或属性长度超了,直接返回非法消息的结果
                beginTimeInLock = 0;
                return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, result);
            case UNKNOWN_ERROR: // 未知错误
                beginTimeInLock = 0;
                return new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, result);
            default: // 默认,按未知错误处理
                beginTimeInLock = 0;
                return new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, result);
        }
		// 记录所花时间
        eclipseTimeInLock = this.defaultMessageStore.getSystemClock().now() - beginLockTimestamp;
        beginTimeInLock = 0; // beginTimeInLock 值置0
    } finally {
         // 释放锁
        putMessageLock.unlock();
    }
 	// 所花时间超过500,就要日志记录下,如果出现很多这种日志,程序员/运维工程师就得警惕起来了,要排查问题
    if (eclipseTimeInLock > 500) {
        log.warn("[NOTIFYME]putMessages in lock cost time(ms)={}, bodyLength={} AppendMessageResult={}", eclipseTimeInLock, messageExtBatch.getBody().length, result);
    }
    // 解锁映射文件
    if (null != unlockMappedFile && this.defaultMessageStore.getMessageStoreConfig().isWarmMapedFileEnable()) {
        this.defaultMessageStore.unlockMappedFile(unlockMappedFile);
    }
	// 能走到这里,证明存储成功了,设置返回成功结果
    PutMessageResult putMessageResult = new PutMessageResult(PutMessageStatus.PUT_OK, result);

    // 统计:增加该topic存储成功次数+批量消息总条数 
    storeStatsService.getSinglePutMessageTopicTimesTotal(messageExtBatch.getTopic()).addAndGet(result.getMsgNum());
        // 统计:增加该topic存储成功消息字节+写入字节的大小
    storeStatsService.getSinglePutMessageTopicSizeTotal(messageExtBatch.getTopic()).addAndGet(result.getWroteBytes());
	// 刷盘,看`章节12.1`
    handleDiskFlush(result, putMessageResult, messageExtBatch);
    // 复制到从节点备份,看`章节12.2`
    handleHA(result, putMessageResult, messageExtBatch);
	// 返回结果
    return putMessageResult;
}
  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

多栖码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值