rocketmq同步刷盘源码分析
文章主要分析的是rocketmq的同步刷盘
//commitlog.java.asyncPutMessage()方法
//提交刷盘的请求,这里封装了一个CompletableFuture,后面可以并行的去处理
CompletableFuture<PutMessageStatus> flushResultFuture = submitFlushRequest(result, putMessageResult, msg);
submitFlushRequest()提交刷盘的请求
public CompletableFuture<PutMessageStatus> submitFlushRequest(AppendMessageResult result, PutMessageResult putMessageResult,
MessageExt messageExt) {
// Synchronization flush 同步刷盘
if (FlushDiskType.SYNC_FLUSH == this.defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) {
//GroupCommitService()
final GroupCommitService service = (GroupCommitService) this.flushCommitLogService;
//这个message是需要等待storeMsg的结果 ,默认是true
if (messageExt.isWaitStoreMsgOK()) {
//封装成一个GroupCommitRequest
//syncFlushTimeout = 1000 * 5; 同步刷盘的时间
GroupCommitRequest request = new GroupCommitRequest(result.getWroteOffset() + result.getWroteBytes(),this.defaultMessageStore.getMessageStoreConfig().getSyncFlushTimeout());
//GroupCommitService提交一个request,见下面分析
service.putRequest(request);
return request.future();
} else {
service.wakeup();
return CompletableFuture.completedFuture(PutMessageStatus.PUT_OK);
}
}
// Asynchronous flush 异步刷盘
else {
//如果是一步刷盘,并且不开启TransientStorePoolEnable
if (!this.defaultMessageStore.getMessageStoreConfig().isTransientStorePoolEnable()) {
//FlushRealTimeService
flushCommitLogService.wakeup();
} else {
commitLogService.wakeup();
}
return CompletableFuture.completedFuture(PutMessageStatus.PUT_OK);
}
}
GroupCommitService 同步刷盘
GroupCommitService继承了FlushCommitLogService,FlushCommitLogService继承了ServiceThread,GroupCommitService就是一个线程,会在初始化的时候启动他,然后调用run方法
class GroupCommitService extends FlushCommitLogService {
//这边使用读写队列,好处是当放任务和拿任务的时候,锁的力度可以变小
private volatile List<GroupCommitRequest> requestsWrite = new ArrayList<GroupCommitRequest>();
private volatile List<GroupCommitRequest> requestsRead = new ArrayList<GroupCommitRequest>();
public synchronized void putRequest(final GroupCommitRequest request) {
//requestsWrite队列里面添加
synchronized (this.requestsWrite) {
this.requestsWrite.add(request);
}
//唤醒waitForRunning
this.wakeup();
}
//requestsWrite放入到requestsRead里面
private void swapRequests() {
List<GroupCommitRequest> tmp = this.requestsWrite;
this.requestsWrite = this.requestsRead;
this.requestsRead = tmp;
}
//真正处理commit的地方
private void doCommit() {
//synchronized防止其他人操作requestsRead
synchronized (this.requestsRead) {
if (!this.requestsRead.isEmpty()) {
//开始刷盘
for (GroupCommitRequest req : this.requestsRead) {
boolean flushOK = false;
for (int i = 0; i < 2 && !flushOK; i++) {
//开始处理 判断当前mappedFileQueue的刷盘的位置是否大于>GroupCommitRequest的刷盘位置
flushOK = CommitLog.this.mappedFileQueue.getFlushedWhere() >= req.getNextOffset();
//如果不是,说明还有消息是没有刷盘的
if (!flushOK) {
//开启刷盘
CommitLog.this.mappedFileQueue.flush(0);
}
}
//返回刷盘的结果
req.wakeupCustomer(flushOK ? PutMessageStatus.PUT_OK : PutMessageStatus.FLUSH_DISK_TIMEOUT);
}
long storeTimestamp = CommitLog.this.mappedFileQueue.getStoreTimestamp();
if (storeTimestamp > 0) {
CommitLog.this.defaultMessageStore.getStoreCheckpoint().setPhysicMsgTimestamp(storeTimestamp);
}
//数据清空一下,全部处理完了
this.requestsRead.clear();
} else {
//直接做一次刷盘单个消息被设置为不同步刷新将进入这个过程,如果没有任务,直接刷新一次
CommitLog.this.mappedFileQueue.flush(0);
}
}
}
public void run() {
CommitLog.log.info(this.getServiceName() + " service started");
while (!this.isStopped()) {
try {
//防止一直处理,休息10ms,见下面解析,执行挖会调用onWaitEnd,
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");
}
@Override
protected void onWaitEnd() {
//交换write和read
this.swapRequests();
}
@Override
public String getServiceName() {
return GroupCommitService.class.getSimpleName();
}
@Override
public long getJointime() {
return 1000 * 60 * 5;
}
}
protected void waitForRunning(long interval) {
if (hasNotified.compareAndSet(true, false)) {
this.onWaitEnd();
return;
}
//entry to wait
waitPoint.reset();
try {
waitPoint.await(interval, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
log.error("Interrupted", e);
} finally {
hasNotified.set(false);
//回调我们的GroupCommitService
this.onWaitEnd();
}
}
flush() 刷盘
public boolean flush(final int flushLeastPages) {
boolean result = true;//返回一个mappedFile就是返回一个commitLog的对象
//获得我们刷盘要的MappedFile
MappedFile mappedFile = this.findMappedFileByOffset(this.flushedWhere, this.flushedWhere == 0);
if (mappedFile != null) {
long tmpTimeStamp = mappedFile.getStoreTimestamp();
//这里去刷新进去,因为是flushLeastPages是0mappedFile.flush(0),这个方法就代表都是强制刷盘
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;
}
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 {
//mappedByteBuffer强制刷新
this.mappedByteBuffer.force();
}
} catch (Throwable e) {
log.error("Error occurred when force data to disk.", e);
}
//更新flushwhere为当前位置
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();
}
//是否值得谁像你
private boolean isAbleToFlush(final int flushLeastPages) {
//刷新的位置
int flush = this.flushedPosition.get();
//当前写的位置
int write = getReadPosition();
if (this.isFull()) {
return true;
}
//如果不是0,会判断是否大于1页,因为我们是0,所以强制刷新
if (flushLeastPages > 0) {
//大于4K了
return ((write / OS_PAGE_SIZE) - (flush / OS_PAGE_SIZE)) >= flushLeastPages;
}
return write > flush;
}
总结:
同步刷盘机制,如果消息是需要等待刷盘的等待结构的,封装成一个GroupCommitRequest,里面有自己消息的偏移量+长度,提交到GroupCommitRequest的write的列表,里面等待GroupCommitRequest的run方法的swap放入到read的队列里面,可以是锁细化,当执行docommit的方法的时候,遍历所有的GroupCommitRequest的任务,然后判断当前的节点时候已经刷完了,如果刷完了就不执行,如果没有刷完就调用CommitLog.this.mappedFileQueue.flush(0),flush(0)就是强制刷新一次,不用等待是否已经超过了一个pageCache,然后返回cf.complete(PUT_OK)
下个文章我们讲rocketmq的异步刷盘机制