1.原則
1.メッセージはどこにありますか?
メッセージが永続化される場所は、実際にはディスク上の次のディレクトリのcommitlogフォルダにあります。
/root/store/commitlog
ソースコードは次のとおりです。
// {@link org.apache.rocketmq.store.config.MessageStoreConfig}
// 数据存储根目录
private String storePathRootDir = System.getProperty("user.home") + File.separator + "store";
// commitlog目录
private String storePathCommitLog = System.getProperty("user.home") + File.separator + "store" + File.separator + "commitlog";
// 每个commitlog文件大小为1GB,超过1GB则创建新的commitlog文件
private int mappedFileSizeCommitLog = 1024 * 1024 * 1024;
たとえば、次のことを確認します。
[root@iZ2ze84zygpzjw5bfcmh2hZ commitlog]# pwd
/root/store/commitlog
[root@iZ2ze84zygpzjw5bfcmh2hZ commitlog]# ll -h
total 400K
-rw-r--r-- 1 root root 1.0G Jun 30 18:21 00000000000000000000
[root@iZ2ze84zygpzjw5bfcmh2hZ commitlog]#
ファイルサイズが1.0Gであり、メッセージが1.0Gを超えて書き込まれると、新しいcommitlogファイルが自動的に作成されることがはっきりとわかります。
2.主要カテゴリーの説明
2.1、MappedFile
上記の00000000000000000000
ファイルなどのcommitlogファイルに対応します。
2.2、MappedFileQueue
でMappedFile
、それがあるフォルダには、ペアが MappedFile
ファイルキューにパッケージ化されています。
2.3、CommitLog
対象となる MappedFileQueue
パッケージの使用。
2、ブローカーはメッセージを受信します
1.コールチェーン
BrokerStartup.start() -》 BrokerController.start() -》 NettyRemotingServer.start() -》 NettyRemotingServer.prepareSharableHandlers() -》 new NettyServerHandler() -》 NettyRemotingAbstract.processMessageReceived()
-》 NettyRemotingAbstract.processRequestCommand() -》 SendMessageProcessor.processRequest()
2、processRequest
SendMessageProcessor.processRequest()
@Override
public RemotingCommand processRequest(ChannelHandlerContext ctx,
RemotingCommand request) throws RemotingCommandException {
RemotingCommand response = null;
try {
// 调用asyncProcessRequest
response = asyncProcessRequest(ctx, request).get();
} catch (InterruptedException | ExecutionException e) {
log.error("process SendMessage error, request : " + request.toString(), e);
}
return response;
}
3、asyncProcessRequest
public CompletableFuture<RemotingCommand> asyncProcessRequest(ChannelHandlerContext ctx,
RemotingCommand request) throws RemotingCommandException {
final SendMessageContext mqtraceContext;
switch (request.getCode()) {
// 表示消费者发送的消息,发送者消费失败会重新发回队列进行消息重试
case RequestCode.CONSUMER_SEND_MSG_BACK:
return this.asyncConsumerSendMsgBack(ctx, request);
default:
// 解析header,也就是我们Producer发送过来的消息都在request里,给他解析到SendMessageRequestHeader对象里去。
SendMessageRequestHeader requestHeader = parseRequestHeader(request);
if (requestHeader == null) {
return CompletableFuture.completedFuture(null);
}
mqtraceContext = buildMsgContext(ctx, requestHeader);
// 将解析好的参数放到SendMessageContext对象里
this.executeSendMessageHookBefore(ctx, request, mqtraceContext);
if (requestHeader.isBatch()) {
// 批处理消息用
return this.asyncSendBatchMessage(ctx, request, mqtraceContext, requestHeader);
} else {
// 非批处理,我们这里介绍的核心。
return this.asyncSendMessage(ctx, request, mqtraceContext, requestHeader);
}
}
}
4、asyncSendMessage
private CompletableFuture<RemotingCommand> asyncSendMessage(ChannelHandlerContext ctx, RemotingCommand request,
SendMessageContext mqtraceContext,
SendMessageRequestHeader requestHeader) {
final byte[] body = request.getBody();
int queueIdInt = requestHeader.getQueueId();
TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic());
// 拼凑message对象
MessageExtBrokerInner msgInner = new MessageExtBrokerInner();
msgInner.setTopic(requestHeader.getTopic());
msgInner.setQueueId(queueIdInt);
msgInner.setBody(body);
msgInner.setFlag(requestHeader.getFlag());
MessageAccessor.setProperties(msgInner, MessageDecoder.string2messageProperties(requestHeader.getProperties()));
msgInner.setPropertiesString(requestHeader.getProperties());
msgInner.setBornTimestamp(requestHeader.getBornTimestamp());
msgInner.setBornHost(ctx.channel().remoteAddress());
msgInner.setStoreHost(this.getStoreHost());
msgInner.setReconsumeTimes(requestHeader.getReconsumeTimes() == null ? 0 : requestHeader.getReconsumeTimes());
CompletableFuture<PutMessageResult> putMessageResult = null;
Map<String, String> origProps = MessageDecoder.string2messageProperties(requestHeader.getProperties());
// 真正接收消息的方法
putMessageResult = this.brokerController.getMessageStore().asyncPutMessage(msgInner);
return handlePutMessageResultFuture(putMessageResult, response, request, msgInner, responseHeader, mqtraceContext, ctx, queueIdInt);
}
この時点で、メッセージの受信は完了し、それらはすべてMessageExtBrokerInnerオブジェクトにカプセル化されています。
3、ブローカーメッセージストレージ(永続性)
1、asyncPutMessage
前のステップでasyncSendMessageを引き続き確認します
@Override
public CompletableFuture<PutMessageResult> asyncPutMessage(MessageExtBrokerInner msg) {
CompletableFuture<PutMessageResult> putResultFuture = this.commitLog.asyncPutMessage(msg);
putResultFuture.thenAccept((result) -> {
......
});
return putResultFuture;
}
2、commitLog.asyncPutMessage
public CompletableFuture<PutMessageResult> asyncPutMessage(final MessageExtBrokerInner msg) {
// 获取最后一个文件,MappedFile就是commitlog目录下的那个0000000000文件
MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile();
try {
// 追加数据到commitlog
result = mappedFile.appendMessage(msg, this.appendMessageCallback);
switch (result.getStatus()) {
......
}
// 将内存的数据持久化到磁盘
CompletableFuture<PutMessageStatus> flushResultFuture = submitFlushRequest(result, putMessageResult, msg);
}
}
3、appendMessagesInner
public AppendMessageResult appendMessagesInner(final MessageExt messageExt, final AppendMessageCallback cb) {
// 将消息写到内存
return cb.doAppend(this.getFileFromOffset(), byteBuffer, this.fileSize - currentPos, (MessageExtBrokerInner) messageExt);
}
4、doAppend
@Override
public AppendMessageResult doAppend(final long fileFromOffset, final ByteBuffer byteBuffer, final int maxBlank,
final MessageExtBrokerInner msgInner) {
// Initialization of storage space
this.resetByteBuffer(msgStoreItemMemory, msgLen);
// 1 TOTALSIZE
this.msgStoreItemMemory.putInt(msgLen);
// 2 MAGICCODE
this.msgStoreItemMemory.putInt(CommitLog.MESSAGE_MAGIC_CODE);
// 3 BODYCRC
this.msgStoreItemMemory.putInt(msgInner.getBodyCRC());
// 4 QUEUEID
this.msgStoreItemMemory.putInt(msgInner.getQueueId());
// 5 FLAG
this.msgStoreItemMemory.putInt(msgInner.getFlag());
// 6 QUEUEOFFSET
this.msgStoreItemMemory.putLong(queueOffset);
// 7 PHYSICALOFFSET
this.msgStoreItemMemory.putLong(fileFromOffset + byteBuffer.position());
// 8 SYSFLAG
this.msgStoreItemMemory.putInt(msgInner.getSysFlag());
// 9 BORNTIMESTAMP
this.msgStoreItemMemory.putLong(msgInner.getBornTimestamp());
// 10 BORNHOST
this.resetByteBuffer(bornHostHolder, bornHostLength);
this.msgStoreItemMemory.put(msgInner.getBornHostBytes(bornHostHolder));
// 11 STORETIMESTAMP
this.msgStoreItemMemory.putLong(msgInner.getStoreTimestamp());
// 12 STOREHOSTADDRESS
this.resetByteBuffer(storeHostHolder, storeHostLength);
this.msgStoreItemMemory.put(msgInner.getStoreHostBytes(storeHostHolder));
// 13 RECONSUMETIMES
this.msgStoreItemMemory.putInt(msgInner.getReconsumeTimes());
// 14 Prepared Transaction Offset
this.msgStoreItemMemory.putLong(msgInner.getPreparedTransactionOffset());
// 15 BODY
this.msgStoreItemMemory.putInt(bodyLength);
if (bodyLength > 0)
this.msgStoreItemMemory.put(msgInner.getBody());
// 16 TOPIC
this.msgStoreItemMemory.put((byte) topicLength);
this.msgStoreItemMemory.put(topicData);
// 17 PROPERTIES
this.msgStoreItemMemory.putShort((short) propertiesLength);
if (propertiesLength > 0)
this.msgStoreItemMemory.put(propertiesData);
final long beginTimeMills = CommitLog.this.defaultMessageStore.now();
// Write messages to the queue buffer
byteBuffer.put(this.msgStoreItemMemory.array(), 0, msgLen);
return result;
}
このステップでは、実際にメッセージをバッファー、つまり、ここではNIOであるmsgStoreItemMemoryに保存します。
private final ByteBuffer msgStoreItemMemory;
5、submitFlushRequest
[2、commitLog.asyncPutMessage]のsubmitFlushRequestメソッドに戻ります。前のメソッドはデータをByteBufferバッファーに書き込むため、次のステップはこのステップでディスクをフラッシュすることです。
public CompletableFuture<PutMessageStatus> submitFlushRequest(AppendMessageResult result, PutMessageResult putMessageResult,
MessageExt messageExt) {
// 同步刷盘
if (FlushDiskType.SYNC_FLUSH == this.defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) {
final GroupCommitService service = (GroupCommitService) this.flushCommitLogService;
if (messageExt.isWaitStoreMsgOK()) {
GroupCommitRequest request = new GroupCommitRequest(result.getWroteOffset() + result.getWroteBytes(),
this.defaultMessageStore.getMessageStoreConfig().getSyncFlushTimeout());
service.putRequest(request);
return request.future();
} else {
service.wakeup();
return CompletableFuture.completedFuture(PutMessageStatus.PUT_OK);
}
}
// 异步刷盘
else {
if (!this.defaultMessageStore.getMessageStoreConfig().isTransientStorePoolEnable()) {
flushCommitLogService.wakeup();
} else {
commitLogService.wakeup();
}
return CompletableFuture.completedFuture(PutMessageStatus.PUT_OK);
}
}
6.非同期ブラッシング
class FlushRealTimeService extends FlushCommitLogService {
@Override
public void run() {
while (!this.isStopped()) {
try {
// 每隔500ms刷一次盘
if (flushCommitLogTimed) {
Thread.sleep(500);
} else {
this.waitForRunning(500);
}
// 调用mappedFileQueue的flush方法
CommitLog.this.mappedFileQueue.flush(flushPhysicQueueLeastPages);
} catch (Throwable e) {
}
}
}
}
- デフォルトでは、500ミリ秒ごとにディスクが更新されていることがわかります。
7、mappedFileQueue.flush
public boolean flush(final int flushLeastPages) {
MappedFile mappedFile = this.findMappedFileByOffset(this.flushedWhere, this.flushedWhere == 0);
if (mappedFile != null) {
// 真正的刷盘操作
int offset = mappedFile.flush(flushLeastPages);
}
}
8、mappedFile.flush
public int flush(final int flushLeastPages) {
if (this.isAbleToFlush(flushLeastPages)) {
try {
if (writeBuffer != null || this.fileChannel.position() != 0) {
// 刷盘 NIO
this.fileChannel.force(false);
} else {
// 刷盘 NIO
this.mappedByteBuffer.force();
}
} catch (Throwable e) {
log.error("Error occurred when force data to disk.", e);
}
}
return this.getFlushedPosition();
}
これまでのところ、それはすべて終わりました。
4、まとめ
インタビューは尋ねられました:メッセージを受け取った後、ブローカーはどのように持続しますか?
回答者:同期と非同期の2つの方法があります。通常、非同期を選択します。同期効率は低くなりますが、信頼性は高くなります。メッセージストレージの一般的な原則は次のとおりです。
コアクラスMappedFileは、各commitlogファイルに対応します。MappedFileQueueはフォルダーに相当し、すべてのファイルを管理します。一部の操作の提供を担当するマネージャーCommitLogオブジェクトもあります。具体的には、ブローカーはメッセージを取得した後、最初にメッセージ、トピック、キューなどをByteBufferに格納し、次にそれをcommitlogファイルに保持します。commitlogファイルのサイズは1Gです。サイズを超えると、nioメソッドを使用して、ストレージ用に新しいcommitlogファイルが作成されます。
5.補足:同期/非同期ブラッシング
1.主なカテゴリ
クラス名 | 説明 | ブラシ性能 |
---|---|---|
CommitRealTimeService | 非同期フラッシュディスク&&オープンバイトバッファ | 最高 |
FlushRealTimeService | 非同期フラッシュディスク&&メモリバイトバッファを閉じる | より高い |
GroupCommitService | 同期フラッシュ。ディスクが正常にフラッシュされた後、メッセージが返されます。 | 最低 |
2.グラフィック
3.同期ブラッシング
3.1、ソースコード
// {@link org.apache.rocketmq.store.CommitLog#submitFlushRequest()}
// Synchronization flush
if (FlushDiskType.SYNC_FLUSH == this.defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) {
// 同步刷盘service -> GroupCommitService
final GroupCommitService service = (GroupCommitService) this.flushCommitLogService;
if (messageExt.isWaitStoreMsgOK()) {
// 数据准备
GroupCommitRequest request = new GroupCommitRequest(result.getWroteOffset() + result.getWroteBytes(),
this.defaultMessageStore.getMessageStoreConfig().getSyncFlushTimeout());
// 将数据对象放到requestsWrite里
service.putRequest(request);
return request.future();
} else {
service.wakeup();
return CompletableFuture.completedFuture(PutMessageStatus.PUT_OK);
}
}
putRequest
public synchronized void putRequest(final GroupCommitRequest request) {
synchronized (this.requestsWrite) {
this.requestsWrite.add(request);
}
// 这里很关键!!!,给他设置成true。然后计数器-1。下面run方法的时候才会进行交换数据且return
if (hasNotified.compareAndSet(false, true)) {
waitPoint.countDown(); // notify
}
}
実行
public void run() {
while (!this.isStopped()) {
try {
// 是同步还是异步的关键方法,也就是说组不阻塞全看这里。
this.waitForRunning(10);
// 真正的刷盘逻辑
this.doCommit();
} catch (Exception e) {
CommitLog.log.warn(this.getServiceName() + " service has exception. ", e);
}
}
}
waitForRunning
protected volatile AtomicBoolean hasNotified = new AtomicBoolean(false);
// 其实就是CountDownLatch
protected final CountDownLatch2 waitPoint = new CountDownLatch2(1);
protected void waitForRunning(long interval) {
// 如果是true,且给他改成false成功的话,则onWaitEnd()且return,但是默认是false,也就是默认情况下这个if不会进。
if (hasNotified.compareAndSet(true, false)) {
this.onWaitEnd();
return;
}
//entry to wait
waitPoint.reset();
try {
// 等待,默认值是1,也就是waitPoint.countDown()一次后就会激活这里。
waitPoint.await(interval, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
log.error("Interrupted", e);
} finally {
// 给状态值设置成false
hasNotified.set(false);
this.onWaitEnd();
}
}
3.2。まとめ
同期ブラッシングの主なプロセスを要約します。
コアクラスはGroupCommitServiceであり、コアメソッドはwaitForRunningです。
- 最初にputRequestメソッドを呼び出してhasNotifiedをtrueに変更し、通知します
waitPoint.countDown()
。つまり、です。 - runメソッドに続く
waitForRunning()
、waitForRunning()
hasNotifiedが真でないかを決定するために、それはそれはない直接的なリターンを遮断待つために、である、交換データへのtrueの場合、リターン・スワップです。 - 最後に、前のステップに戻り、ブロッキングがない場合は、doCommitを呼び出して実際の更新を実行するのが論理的です。
4.非同期ブラッシング
4.1、ソースコード
コアクラスは次のとおりです。FlushRealTimeService
// {@link org.apache.rocketmq.store.CommitLog#submitFlushRequest()}
// Asynchronous flush
if (!this.defaultMessageStore.getMessageStoreConfig().isTransientStorePoolEnable()) {
flushCommitLogService.wakeup();
} else {
commitLogService.wakeup();
}
return CompletableFuture.completedFuture(PutMessageStatus.PUT_OK);
実行
// {@link org.apache.rocketmq.store.CommitLog.FlushRealTimeService#run()}
class FlushRealTimeService extends FlushCommitLogService {
@Override
public void run() {
while (!this.isStopped()) {
try {
// 每隔500ms刷一次盘
if (flushCommitLogTimed) {
Thread.sleep(500);
} else {
// 根上面同步刷盘调用的是同一个方法,区别在于这里没有将hasNotified变为true,也就是还是默认的false,那么waitForRunning方法内部的第一个判断就不会走,就不会return掉,就会进行下面的await方法阻塞,默认阻塞时间是500毫秒。也就是默认500ms刷一次盘。
this.waitForRunning(500);
}
// 调用mappedFileQueue的flush方法
CommitLog.this.mappedFileQueue.flush(flushPhysicQueueLeastPages);
} catch (Throwable e) {
}
}
}
}
コアクラス#メソッド:FlushRealTimeService#run()
flushCommitLogTimed
trueかどうかを判断し、デフォルトはfalseです。trueの場合は、直接スリープ(500ms)してからmappedFileQueue.flush()
、ディスクをフラッシュします。- falseの場合は、次のように入力し
waitForRunning(500)
ます。同期フラッシュとの違いの鍵は次のとおりです。同期フラッシュの前に、hasNotifiedがtrueに変更されるため、一連の小さなトリックが直接行われます。はいreturn+doCommit
、非同期がここで直接呼び出されます。あるwaitForRunning(500)
何の権利が、この前。hasNotifiedの操作は、それが返されませんが、継続されますので、waitPoint.await(500, TimeUnit.MILLISECONDS);
自動的に500ミリ秒後に目を覚ますと、ディスクをフラッシュし、500ミリ秒間ブロックするように。つまり、ディスクが非同期で更新される場合、デフォルトでは500msでディスクを1回更新します。