一、刷新服務(wù)
二、GroupCommitService
(一) GroupCommitService核心屬性
List<GroupCommitRequest> requestsWrite:請求寫入隊列
List<GroupCommitRequest> requestsRead:請求讀隊列
上面兩個隊列會進行交換,每次刷盤請求是寫到requestsWrite隊列中,GroupCommitService處理刷盤請求之前,會執(zhí)行隊列交換
(二) 添加刷盤請求
當消息寫到緩沖池以后,會調(diào)用下面方面進行磁盤刷新和主從復(fù)制
handleDiskFlush(result, putMessageResult, msg);
handleHA(result, putMessageResult, msg);
如果刷盤方式是同步,那么就構(gòu)造一個GroupCommitRequest請求,GroupCommitService服務(wù)異步刷新數(shù)據(jù),如果是異步刷盤,就喚醒異步刷盤服務(wù)
public void handleDiskFlush(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());
// 將刷盤請求放到隊列中
service.putRequest(request);
// 使用了CountDownLatch機制,等待刷盤成功
boolean flushOK = request.waitForFlush(this.defaultMessageStore.getMessageStoreConfig().getSyncFlushTimeout());
if (!flushOK) {
log.error("do groupcommit, wait for flush failed, topic: " + messageExt.getTopic() + " tags: " + messageExt.getTags()
+ " client address: " + messageExt.getBornHostString());
putMessageResult.setPutMessageStatus(PutMessageStatus.FLUSH_DISK_TIMEOUT);
}
} else {
service.wakeup();
}
}
// 異步刷盤
else {
if (!this.defaultMessageStore.getMessageStoreConfig().isTransientStorePoolEnable()) {
flushCommitLogService.wakeup();
} else {
commitLogService.wakeup();
}
}
}
(三) 刷盤請求處理
GroupCommitService從隊列中取出刷盤請求,并執(zhí)行刷盤操作
public void run() {
CommitLog.log.info(this.getServiceName() + " service started");
?
while (!this.isStopped()) {
try {
// 每隔10ms,交換讀寫隊列
this.waitForRunning(10);
// 真正的刷盤
this.doCommit();
} catch (Exception e) {
CommitLog.log.warn(this.getServiceName() + " service has exception. ", e);
}
}
?
// Under normal circumstances shutdown, wait for the arrival of the
// request, and then flush
try {
Thread.sleep(10);
} catch (InterruptedException e) {
CommitLog.log.warn("GroupCommitService Exception, ", e);
}
?
synchronized (this) {
this.swapRequests();
}
?
this.doCommit();
?
CommitLog.log.info(this.getServiceName() + " service end");
}
每隔10ms,會進行一次讀寫隊列的交換,并調(diào)用doCommit()方法,執(zhí)行真正的磁盤刷新操作。
private void doCommit() {
synchronized (this.requestsRead) {
// 讀隊列不為空,遍歷所有刷盤請求,執(zhí)行數(shù)據(jù)刷盤操作
if (!this.requestsRead.isEmpty()) {
for (GroupCommitRequest req : this.requestsRead) {
// There may be a message in the next file, so a maximum of
// two times the flush
boolean flushOK = false;
for (int i = 0; i < 2 && !flushOK; i++) {
flushOK = CommitLog.this.mappedFileQueue.getFlushedWhere() >= req.getNextOffset();
if (!flushOK) {
CommitLog.this.mappedFileQueue.flush(0);
}
}
req.wakeupCustomer(flushOK);
}
// 設(shè)置檢查點
long storeTimestamp = CommitLog.this.mappedFileQueue.getStoreTimestamp();
if (storeTimestamp > 0) {
CommitLog.this.defaultMessageStore.getStoreCheckpoint().setPhysicMsgTimestamp(storeTimestamp);
}
// 處理完刷盤請求以后,執(zhí)行隊列清空操作
this.requestsRead.clear();
} else {
// Because of individual messages is set to not sync flush, it
// will come to this process
CommitLog.this.mappedFileQueue.flush(0);
}
}
}
三、FlushRealTimeService
(一) 核心屬性
long lastFlushTimestamp:最后一次刷盤時間戳
long printTimes = 0:
(二) 異步刷盤流程
public void run() {
while (!this.isStopped()) {
// 刷新策略(默認是實時刷盤)
boolean flushCommitLogTimed = CommitLog.this.defaultMessageStore.getMessageStoreConfig().isFlushCommitLogTimed();
// 刷盤間隔
int interval = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushIntervalCommitLog();
// 每次刷盤至少需要多少個page(默認是4個)
int flushPhysicQueueLeastPages = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushCommitLogLeastPages();
// 徹底刷盤間隔時間(默認10s)
int flushPhysicQueueThoroughInterval =
CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushCommitLogThoroughInterval();
?
boolean printFlushProgress = false;
// Print flush progress
long currentTimeMillis = System.currentTimeMillis();
// 當前時間 >(最后一次刷盤時間 + 徹底刷盤間隔時間(10s)),則將最新一次刷盤時間更新為當前時間
if (currentTimeMillis >= (this.lastFlushTimestamp + flushPhysicQueueThoroughInterval)) {
this.lastFlushTimestamp = currentTimeMillis;
flushPhysicQueueLeastPages = 0;
printFlushProgress = (printTimes++ % 10) == 0;
}
?
try {
if (flushCommitLogTimed) {
Thread.sleep(interval);
} else {
this.waitForRunning(interval);
}
?
if (printFlushProgress) {
this.printFlushProgress();
}
?
long begin = System.currentTimeMillis();
CommitLog.this.org.apache.rocketmq.store.MappedFileQueue#flush;
long storeTimestamp = CommitLog.this.mappedFileQueue.getStoreTimestamp();
if (storeTimestamp > 0) {
CommitLog.this.defaultMessageStore.getStoreCheckpoint().setPhysicMsgTimestamp(storeTimestamp);
}
long past = System.currentTimeMillis() - begin;
if (past > 500) {
log.info("Flush data to disk costs {} ms", past);
}
} catch (Throwable e) {
CommitLog.log.warn(this.getServiceName() + " service has exception. ", e);
this.printFlushProgress();
}
}
?
// Normal shutdown, to ensure that all the flush before exit
boolean result = false;
for (int i = 0; i < RETRY_TIMES_OVER && !result; i++) {
result = CommitLog.this.mappedFileQueue.flush(0);
CommitLog.log.info(this.getServiceName() + " service shutdown, retry " + (i + 1) + " times " + (result ? "OK" : "Not OK"));
}
?
this.printFlushProgress();
?
CommitLog.log.info(this.getServiceName() + " service end");
}
如果是實時刷盤,每隔一定時間間隔,該線程休眠500毫秒,如果不是實時刷盤,則調(diào)用waitForRunning,即每隔500毫秒或該刷盤服務(wù)線程調(diào)用了wakeup()方法之后結(jié)束阻塞。最后調(diào)用 CommitLog.this.mappedFileQueue.flush(flushPhysicQueueLeastPages)進行刷盤 。
數(shù)據(jù)刷盤調(diào)用了MappedFileQueue.flush(int flushLeastPages)方法,進行刷盤
public boolean flush(final int flushLeastPages) {
boolean result = true;
// 根據(jù)offset,找到對應(yīng)的MappedFile
MappedFile mappedFile = this.findMappedFileByOffset(this.flushedWhere, this.flushedWhere == 0);
if (mappedFile != null) {
long tmpTimeStamp = mappedFile.getStoreTimestamp();
// 調(diào)用MappedFile的刷盤方法
int offset = mappedFile.flush(flushLeastPages);
long where = mappedFile.getFileFromOffset() + offset;
result = where == this.flushedWhere;
this.flushedWhere = where;
if (0 == flushLeastPages) {
this.storeTimestamp = tmpTimeStamp;
}
}
return result;
}
數(shù)據(jù)刷盤,最終是調(diào)用 了MappedFile.flush(int flushLeastPages)方法實現(xiàn)數(shù)據(jù)持久化,具體實現(xiàn)如下:
/**
* @return The current flushed position
*/
public int flush(final int flushLeastPages) {
// 判斷是否可以刷盤
if (this.isAbleToFlush(flushLeastPages)) {
if (this.hold()) {
int value = getReadPosition();
try {
//We only append data to fileChannel or mappedByteBuffer, never both.
if (writeBuffer != null || this.fileChannel.position() != 0) {
this.fileChannel.force(false);
} else {
this.mappedByteBuffer.force();
}
} catch (Throwable e) {
log.error("Error occurred when force data to disk.", e);
}
this.flushedPosition.set(value);
this.release();
} else {
log.warn("in flush, hold failed, flush offset = " + this.flushedPosition.get());
this.flushedPosition.set(getReadPosition());
}
}
return this.getFlushedPosition();
}
調(diào)用isAbleToFlush方法判斷是否可以刷盤,判斷邏輯如下:
private boolean isAbleToFlush(final int flushLeastPages) {
int flush = this.flushedPosition.get();
int write = getReadPosition();
// 如果文件已滿
if (this.isFull()) {
return true;
}
// 需要刷盤頁數(shù) > 最少刷盤頁數(shù)
if (flushLeastPages > 0) {
return ((write / OS_PAGE_SIZE) - (flush / OS_PAGE_SIZE)) >= flushLeastPages;
}
return write > flush;
}