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的异步刷盘机制

  • 27
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值