【hadoop二次開發】根據彙報的資料塊判斷是否可以離開安全模式

Aponson發表於2020-10-06

008-hadoop二次開發

在原始碼檔案FSNamesystem.java執行完nnResourceChecker = new NameNodeResourceChecker(conf);
立馬執行checkAvailableResources(),檢查可用資源是否足夠:如果不夠,日誌列印警告資訊,然後進入安全模式。
然後

  /**
   * 磁碟資源不足的情況下,任何對後設資料修改所產生的日誌都無法確保能夠寫入到磁碟,
   * 即新產生的edits log和fsimage都無法確保寫入磁碟。所以要進入安全模式,
   * 來禁止後設資料的變動以避免往磁碟寫入新的日誌資料
   * */
  assert safeMode != null && !isPopulatingReplQueues();
  /**
   * Check if replication queues are to be populated
   * @return true when node is HAState.Active and not in the very first safemode
   */
  /**這個方法是用來檢查副本佇列是否應該被同步/複製*/
  @Override
  public boolean isPopulatingReplQueues() {
    if (!shouldPopulateReplQueues()) {
      return false;
    }
    return initializedReplQueues;
  }

設定資料塊彙報

//處於一個等待彙報blocks的狀態
prog.setTotal(Phase.SAFEMODE, STEP_AWAITING_REPORTED_BLOCKS, getCompleteBlocksTotal());
  /**
   * NN端的block有四種狀態:
   * 1、UnderConstruction(正在被寫入的狀態)
   * 2、UnderRecovery(正在被恢復的塊)
   * 3、Committed(已經確定好它的位元組大小與generation stamp值(可理解為版本號))
   * 4、Complete(寫入執行操作結束狀態)
   * 啟動的時候 ,namenode認為只有block狀態為Complete,才會被讀取
   *
   * 獲取系統中的COMPLETE塊總數。
   * 對於安全模式,僅計算完整塊。
   */
  private long getCompleteBlocksTotal() {
    // Calculate number of blocks under construction
    long numUCBlocks = 0;
    readLock();
    //獲取所有正在構建的block塊
    //leaseManager 檔案租約(為每一個客戶端和檔案構建關聯)
    numUCBlocks = leaseManager.getNumUnderConstructionBlocks();
    try {
      return getBlocksTotal() - numUCBlocks;
    } finally {
      readUnlock();
    }
  }

設定所有的block,用於後面判斷是否進入安全模式setBlockTotal(),

  /**
   * Set the total number of blocks in the system. 
   */
  public void setBlockTotal() {
    // safeMode is volatile, and may be set to null at any time
    SafeModeInfo safeMode = this.safeMode;
    if (safeMode == null)
      return;
    //TODO 設定安全模式
    //getCompleteBlocksTotal 獲取所有正常使用的block個數
    safeMode.setBlockTotal((int)getCompleteBlocksTotal());
  }

通過SafeModeInfo safeMode = this.safeMode;拿到狀態資訊,

/** Time when threshold was reached.
 * <br> -1 safe mode is off
 * <br> 0 safe mode is on, and threshold is not reached yet
 * <br> >0 safe mode is on, but we are in extension period 
 */
private long reached = -1;  
/**
 * 1、得到滿足安全模式閾值條件所需的塊數
 * 2、填充複製佇列之前所需的塊數
 * 3、檢查是否需要進入安全模式
 */
private synchronized void setBlockTotal(int total) {
  this.blockTotal = total;//彙報過來的blocks(complete狀態)個數
  //滿足安全模式閾值條件所需的塊數  blockTotal * 0.999f  1000 * 0.999 = 999
  this.blockThreshold = (int) (blockTotal * threshold);
  //填充複製佇列之前所需的塊數 blockTotal * 0.999f
  this.blockReplQueueThreshold = (int) (blockTotal * replQueueThreshold);
  if (haEnabled) {
    // After we initialize the block count, any further namespace
    // modifications done while in safe mode need to keep track
    // of the number of total blocks in the system.
    this.shouldIncrementallyTrackBlocks = true;
  }
  /**
    * blockSafe是datanode向namenode進行彙報的塊個數,通過incrementSafeBlockCount方法,不斷的疊加起來的
    * 通過decrementSafeBlockCount,當datanode向namenode彙報刪除資料塊的時候,此處就對blockSafe減小
     * */
    if(blockSafe < 0)
      this.blockSafe = 0;

    checkMode();
  }
/**
 * 用於檢查安全模式的狀態:
 * 1、判斷閾值係數是否滿足進入安全模式:needEnter
 * 對於離開安全模式,有兩個條件判斷:
 * 1、判斷係數是否滿足離開安全模式
 * 2、啟動SafeModeMonitor執行緒,每隔1秒去檢視下,是否可以退出安全模式
 */
private void checkMode() {

  /**assert 如果<boolean表示式>為true,則程式繼續執行。
   如果為false,則程式丟擲AssertionError,並終止執行。*/
  assert hasWriteLock();//安全模式下,需要寫鎖,禁止寫入

  //如果當前節點已經是active狀態,則不需要在檢查了,直接返回
  if (inTransitionToActive()) {
    return;
  }

  //TODO 判斷是否進入安全模式
  if (smmthread == null && needEnter()) {
    //進入安全模式
    enter();
    /**
     * canInitializeReplQueues:判斷namenode已經接收到的blockSafe塊數量是否達到了
     * 恢復複製和刪除資料塊資料塊數量
     * */
    if (canInitializeReplQueues() && !isPopulatingReplQueues()
        && !haEnabled) {
      initializeReplQueues();
    }
    reportStatus("STATE* Safe mode ON.", false);
    return;
  }
  /**
   * extension處於安全模式的時間(滿足了退出安全模式條件 ,此時還是處於安全模式的時間)
   * threshold安全係數
   * isOn 當前的狀態(reached)是否小於0
   * */
  if (!isOn() || extension <= 0 || threshold <= 0) {  // don't need to wait
    this.leave(); // 退出安全模式
    return;
  }
  if (reached > 0) {  // threshold has already been reached before
    reportStatus("STATE* Safe mode ON.", false);
    return;
  }
  /**TODO 啟動SafeModeMonitor執行緒,每隔1秒去檢視下,是否可以退出安全模式*/
  reached = monotonicNow();
  reachedTimestamp = now();
  if (smmthread == null) {
    smmthread = new Daemon(new SafeModeMonitor());
    smmthread.start();
    reportStatus("STATE* Safe mode extension entered.", true);
  }

  // check if we are ready to initialize replication queues
  if (canInitializeReplQueues() && !isPopulatingReplQueues() && !haEnabled) {
    initializeReplQueues();
  }
}

是否進入安全模式的enter方法

/**
 * Enter safe mode.
 */
private void enter() {
  /**
   *  reached
   * <br> -1 退出安全模式
   * <br> 0 進入安全模式
   * */
  this.reached = 0;
  this.reachedTimestamp = 0;
}

怎麼進入安全模式?
首先守護執行緒smmthread為空,並且needEnter()為真,

/** 
 * There is no need to enter safe mode 
 * if DFS is empty or {@link #threshold} == 0
 */
/**
 * 進入安全模式有3個條件,任何一個滿足,都會進入安全模式
 * 條件1:threshold != 0 && blockSafe < blockThreshold)
 *      this.blockThreshold = (int) (blockTotal * threshold);
 *      也就是說:如果有1000個block,那麼閾值blockThreshold=999
 *      但是如果叢集啟動,datanode彙報過來的累計的塊數小於<999,那麼就會進入安全模式
 * 條件2:datanodeThreshold != 0 && getNumLiveDataNodes() < datanodeThreshold
 *       啟動叢集后,存活的datanode個數小於datanodeThreshold(預設0),則進入安全模式
 *
 * 條件3:!nameNodeHasResourcesAvailable()
 *       檢查磁碟,是否大於100M
 *
 * */
private boolean needEnter() {
  //假如1000個資料塊 , blockSafe < 999
  return (threshold != 0 && blockSafe < blockThreshold) ||
    (datanodeThreshold != 0 && getNumLiveDataNodes() < datanodeThreshold) ||
    (!nameNodeHasResourcesAvailable());
}

是否離開安全模式中的isOn方法

/**
 * Check if safe mode is on.
 * @return true if in safe mode
 */
private synchronized boolean isOn() {
  doConsistencyCheck();
  //安全模式化 reached = 0
  //非  reached = -1
  return this.reached >= 0;
}

接下來建立守護執行緒
進入SafeModeMonitor執行緒,實現了Runnable介面,在run方法中每個1s鍾迴圈檢查是否要退出安全模式,整體加了寫鎖,最後釋放寫鎖。

    @Override
    public void run() {
      while (fsRunning) {
        writeLock();
        try {
          if (safeMode == null) { // Not in safe mode.
            break;
          }
          //TODO 判斷是否滿足退出安全模式
          if (safeMode.canLeave()) {
            // Leave safe mode.
            safeMode.leave();//方法中將 reached = -1;
            smmthread = null;//將守護執行緒置空
            break;
          }
        } finally {
          writeUnlock();
        }

        try {
          Thread.sleep(recheckInterval);//TODO 每隔1s檢查一遍是否滿足條件
        } catch (InterruptedException ie) {
          // Ignored
        }
      }
      if (!fsRunning) {
        LOG.info("NameNode is being shutdown, exit SafeModeMonitor thread");
      }
    }
  }

若退出安全模式,需滿足safeMode.canLeave()為true,

/**
 * 通過3個條件來判斷是否可以離開安全模式:
 * 1、reached是否為-1
 * 2、在滿足最小副本條件之後,namenode還需要處於安全模式的時間(30s)
 * 3、needEnter裡面的3個條件
 * */
private synchronized boolean canLeave() {
  //reached == -1是離開safemode
  if (reached == 0) {
    return false;
  }

  //extension 預設30s,也就是滿足最低副本系數之後,離開安全模式的時間,這個時間用於等待剩餘資料節點的資料塊上報
  //這裡的reached是來自於建立守護執行緒前reached = monotonicNow();記錄一個當前時間
  //此處的monotonicNow()又拿到一個當前時間
  if (monotonicNow() - reached < extension) {
    reportStatus("STATE* Safe mode ON, in safe mode extension.", false);
    return false;
  }

  if (needEnter()) {
    reportStatus("STATE* Safe mode ON, thresholds not met.", false);
    return false;
  }

  return true;
}

monotonicNow()保障時間的準確性

  public static long monotonicNow() {
    final long NANOSECONDS_PER_MILLISECOND = 1000000;

    return System.nanoTime() / NANOSECONDS_PER_MILLISECOND;
  }

最後

//TODO 啟動BlockManager裡面關於block副本處理的後臺執行緒
//啟用BlockManager
blockManager.activate(conf);

相關文章