书接第九章
的消息处理,最终都需要将消息存储起来,从源码的角度,针对单个消息和批量消息,分别续接章节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;
}