1. 程式人生 > 其它 >hbase原始碼系列(六)HMaster啟動過程

hbase原始碼系列(六)HMaster啟動過程

  這一章是server端開始的第一章,有興趣的朋友先去看一下hbase的架構圖,我專門從網上弄下來的。

  按照HMaster的run方法的註釋,我們可以瞭解到它的啟動過程會去做以下的動作。

 * <li>阻塞直到變成ActiveMaster
 * <li>結束初始化操作
 * <li>迴圈
 * <li>停止服務並執行清理操作* </ol>

  HMaster是沒有單點問題是,因為它可以同時啟動多個HMaster,然後通過zk的選舉演算法選出一個HMaster來。

  我們首先來看看這個阻塞直到變成ActiveMaster的過程吧。

  1、如果不是master的話,就一直睡,isActiveMaster的判斷條件是,在zk當中沒有master節點,如果沒有就一直等待。master節點建立之後,就開始向Master衝刺了,先到先得。

  2、嘗試著在master節點下面把自己的ServerName給加上去,如果加上去了,它就成為master了,成為master之後,就把自己從備份節點當中刪除。

  3、如果沒有成為master,把自己新增到備份節點,同時檢查一下當前的master節點,如果是和自己一樣,那就是出現異常了,明明是設定成功了,確說不成功,接下來它就會一直等待,等到master死掉。

  成為master之後的結束初始化操作,這才是重頭戲啊,前面的都是小意思,例項化的程式碼我就補貼了,看著也沒啥意思,就把這些屬性貼出來吧,讓大家認識認識。

 /** 專門負責master和hdfs互動的類  */
  private MasterFileSystem fileSystemManager;

  /** 專門用於管理Region Server的管理器  */
  ServerManager serverManager;

  /** 專門用於管理zk當中nodes的節點  */
  AssignmentManager assignmentManager;

  /** 負責負載均衡的類  */
  private LoadBalancer balancer;

  /** 負責讀取在hdfs上面的表定義的類 */
  private TableDescriptors tableDescriptors;

  /** 表級別的分散式鎖,專門負責監控模式的變化  */
  private TableLockManager tableLockManager;

   /** 負責監控表的備份 */
  private SnapshotManager snapshotManager;

  這些類都會被例項化,具體的順序就不講了,這個不是特別重要。開學啦,等到region server過來報道,還要記錄一下在zk當中註冊了的,但是沒有在master這裡報道的,不做處理。

    // 等待region server過來註冊,至少要有一個啟動了
    this.serverManager.waitForRegionServers(status);
    // 檢查在zk當中註冊了,但是沒在master這裡註冊的server
    for (ServerName sn: this.regionServerTracker.getOnlineServers()) {
      if (!this.serverManager.isServerOnline(sn)
          && serverManager.checkAlreadySameHostPortAndRecordNewServer(
              sn, ServerLoad.EMPTY_SERVERLOAD)) {
        LOG.info("Registered server found up in zk but who has not yet "
          + "reported in: " + sn);
      }
    }

分配META表前的準備工作,Split Meta表的日誌

  okay,下面是重頭戲了,準備分配meta表了,先啟動個計時器。

// 啟動超時檢查器了哦
if (!masterRecovery) {
    this.assignmentManager.startTimeOutMonitor();
}

   上程式碼,從日誌檔案裡面找出來掛了的server,然後對這些server做處理。

  // 從WALs目錄下找出掛了的機器
    Set<ServerName> previouslyFailedServers = this.fileSystemManager
        .getFailedServersFromLogFolders();
    // 刪除之前執行的時候正在恢復的region,在zk的recovering-regions下所有的region節點一個不留
    this.fileSystemManager.removeStaleRecoveringRegionsFromZK(previouslyFailedServers);

    // 獲取就的meta表的位置,如果在已經掛了的機器上
    ServerName oldMetaServerLocation = this.catalogTracker.getMetaLocation();
    //如果meta表在之前掛了的server上面,就需要把meta表的日誌從日誌檔案裡面單獨拿出來
    if (oldMetaServerLocation != null && previouslyFailedServers.contains(oldMetaServerLocation)) {
      splitMetaLogBeforeAssignment(oldMetaServerLocation);
    }

  F3進入getFailedServersFromLogFolders方法。

     //遍歷WALs下面的檔案
        FileStatus[] logFolders = FSUtils.listStatus(this.fs, logsDirPath, null);//獲取線上的server的集合
        Set<ServerName> onlineServers = ((HMaster) master).getServerManager().getOnlineServers().keySet();
        for (FileStatus status : logFolders) {
          String sn = status.getPath().getName();
          //如果目錄名裡面包含-splitting,就是正在split的日誌
          if (sn.endsWith(HLog.SPLITTING_EXT)) {
            sn = sn.substring(0, sn.length() - HLog.SPLITTING_EXT.length());
          }
          //把字串的機器名轉換成ServerName
          ServerName serverName = ServerName.parseServerName(sn);
          //如果線上的機器裡面不包括這個ServerName就認為它是掛了
          if (!onlineServers.contains(serverName)) {
            serverNames.add(serverName);
          } else {
            LOG.info("Log folder " + status.getPath() + " belongs to an existing region server");
          }
        }

  從程式碼上面看得出來,從WALs日誌的目錄下面找,目錄名稱裡面就包括ServerName,取出來和線上的Server對比一下,把不線上的加到集合裡面,最後返回。看來這個目錄下都是出了問題的Server才會在這裡混。

  我們接著回到上面的邏輯,查出來失敗的Server之後,從zk當中把之前的Meta表所在的位置取出來,如果Meta表在這些掛了的Server裡面,就糟糕了。。得啟動恢復措施了。。。先把META表的日誌從日誌堆裡找出來。我們進入splitMetaLogBeforeAssignment這個方法裡面看看吧。

 private void splitMetaLogBeforeAssignment(ServerName currentMetaServer) throws IOException {
    //該引數預設為false
    if (this.distributedLogReplay) {
      // In log replay mode, we mark hbase:meta region as recovering in ZK
      Set<HRegionInfo> regions = new HashSet<HRegionInfo>();
      regions.add(HRegionInfo.FIRST_META_REGIONINFO);
      this.fileSystemManager.prepareLogReplay(currentMetaServer, regions);
    } else {
      // In recovered.edits mode: create recovered edits file for hbase:meta server
      this.fileSystemManager.splitMetaLog(currentMetaServer);
    }
  }

  可以看出來這裡面有兩種模式,分散式檔案恢復模式,通過zk來恢復,還有一種是recovered.edit模式,通過建立recovered.edits檔案來恢復。檔案恢復是通過hbase.master.distributed.log.replay引數來設定,預設是false,走的recovered.edit模式。看得出來,這個函式是為恢復做準備工作的,如果是分散式模式,就執行prepareLogReplay準備日誌恢復,否則就開始建立recovered.edits恢復檔案。

  (a)prepareLogReplay方法當中,把HRegionInfo.FIRST_META_REGIONINFO這個region新增到了recovering-regions下面,置為恢復中的狀態。

  (b)下面看看splitMetaLog吧,它是通過呼叫這個方法來執行split日誌的,通過filter來過濾META或者非META表的日誌,META表的日誌以.meta結尾。

public void splitLog(final Set<ServerName> serverNames, PathFilter filter) throws IOException {
    long splitTime = 0, splitLogSize = 0;
    //修改WALs日誌目錄的名稱,在需要分裂的目錄的名稱後面加上.splitting,準備分裂
    List<Path> logDirs = getLogDirs(serverNames);
    //把這些掛了的server記錄到splitLogManager的deadWorkers的列表
    splitLogManager.handleDeadWorkers(serverNames);
    splitTime = EnvironmentEdgeManager.currentTimeMillis();
    //split日誌
    splitLogSize = splitLogManager.splitLogDistributed(serverNames, logDirs, filter);
    splitTime = EnvironmentEdgeManager.currentTimeMillis() - splitTime;
    //記錄split結果到統計資料當中
    if (this.metricsMasterFilesystem != null) {
      if (filter == META_FILTER) {
        this.metricsMasterFilesystem.addMetaWALSplit(splitTime, splitLogSize);
      } else {
        this.metricsMasterFilesystem.addSplit(splitTime, splitLogSize);
      }
    }
  }

  上面也帶了不少註釋了,不廢話了,進splitLogDistributed裡面瞅瞅吧。

public long splitLogDistributed(final Set<ServerName> serverNames, final List<Path> logDirs,
      PathFilter filter) throws IOException {//讀取檔案
    FileStatus[] logfiles = getFileList(logDirs, filter);long totalSize = 0;
    //任務跟蹤器,一個batch包括N個任務,最後統計batch當中的總數
    TaskBatch batch = new TaskBatch();
    Boolean isMetaRecovery = (filter == null) ? null : false;
    for (FileStatus lf : logfiles) {
      totalSize += lf.getLen();
      //獲得root後面的相對路徑
      String pathToLog = FSUtils.removeRootPath(lf.getPath(), conf);
      //把任務插入到Split任務列表當中
      if (!enqueueSplitTask(pathToLog, batch)) {
        throw new IOException("duplicate log split scheduled for " + lf.getPath());
      }
    }
    //等待任務結束
    waitForSplittingCompletion(batch, status);
    if (filter == MasterFileSystem.META_FILTER)
      isMetaRecovery = true;
    }
    //清理recovering的狀態,否則region server不讓訪問正在恢復當中的region
    this.removeRecoveringRegionsFromZK(serverNames, isMetaRecovery);

    if (batch.done != batch.installed) {
      //啟動的任務和完成的任務不相等
      batch.isDead = true;
      String msg = "error or interrupted while splitting logs in "
        + logDirs + " Task = " + batch;
      throw new IOException(msg);
    }
    //最後清理日誌
    for(Path logDir: logDirs){try {
        if (fs.exists(logDir) && !fs.delete(logDir, false)) {
        }
      } catch (IOException ioe) {
      }
    }
    status.markComplete(msg);
    return totalSize;
  }

  它的所有的檔案的split檔案都插入到一個split佇列裡面去,然後等待結束,這裡面有點兒繞了,它是到zk的splitWALs節點下面為這個檔案建立一個節點,不是原來的相對路徑名,是URLEncoder.encode(s, "UTF-8")加密過的。

  呵呵,看到這裡是不是要暈了呢,它是在zk裡面建立一個節點,然後不幹活,當然不是啦,在每個Region Server裡面讀會啟動一個SplitLogWorker去負責處理這下面的日誌。split處理過程在HLogSplitter.splitLogFile方法裡面,具體不講了,它會把恢復檔案在region下面生成一個recovered.edits目錄裡面。

分配META表

  下面就開始指派Meta表的region啦。

void assignMeta(MonitoredTask status)
      throws InterruptedException, IOException, KeeperException {
    // Work on meta region
    int assigned = 0;
    ServerName logReplayFailedMetaServer = null;
    //在RegionStates裡面狀態狀態,表名該region正在變化當中
    assignmentManager.getRegionStates().createRegionState(HRegionInfo.FIRST_META_REGIONINFO);
    //處理meta表第一個region,重新指派
    boolean rit = this.assignmentManager.processRegionInTransitionAndBlockUntilAssigned(HRegionInfo.FIRST_META_REGIONINFO);
    //這個應該是meta表,hbase:meta,等待它在zk當中可以被訪問
    boolean metaRegionLocation = this.catalogTracker.verifyMetaRegionLocation(timeout);
    if (!metaRegionLocation) {
      assigned++;
      if (!rit) {
        // 沒分配成功,又得回頭再做一遍準備工作
        ServerName currentMetaServer = this.catalogTracker.getMetaLocation();
        if (currentMetaServer != null) {
          if (expireIfOnline(currentMetaServer)) {
            splitMetaLogBeforeAssignment(currentMetaServer);
            if (this.distributedLogReplay) {
              logReplayFailedMetaServer = currentMetaServer;
            }
          }
        }
        //刪掉zk當中的meta表的位置,再分配
        assignmentManager.assignMeta();
      }
    } else {
      //指派了,就更新一下它的狀態為online
      this.assignmentManager.regionOnline(HRegionInfo.FIRST_META_REGIONINFO,this.catalogTracker.getMetaLocation());
    }
    //在zk當中啟用meta表
    enableMeta(TableName.META_TABLE_NAME);

    // 啟動關機處理執行緒
    enableServerShutdownHandler(assigned != 0);

    if (logReplayFailedMetaServer != null) {
      // 這裡不是再來一次,注意了啊,這個是分散式模式狀態下要進行的一次meta表的日誌split,
      //回頭看一下這個變數啥時候賦值就知道了
      this.fileSystemManager.splitMetaLog(logReplayFailedMetaServer);
    }
  }

  歷經千辛萬苦跟蹤到了這個方法裡面,通過RPC,向隨機抽出來的Region Server傳送請求,讓它開啟region。

public RegionOpeningState sendRegionOpen(final ServerName server,
      HRegionInfo region, int versionOfOfflineNode, List<ServerName> favoredNodes)
              throws IOException {
    AdminService.BlockingInterface admin = getRsAdmin(server);
    if (admin == null) {return RegionOpeningState.FAILED_OPENING;
    }
    //構建openRegion請求,
    OpenRegionRequest request =
      RequestConverter.buildOpenRegionRequest(region, versionOfOfflineNode, favoredNodes);
    try {
      //呼叫指定的Region Server的openRegion方法
      OpenRegionResponse response = admin.openRegion(null, request);
      return ResponseConverter.getRegionOpeningState(response);
    } catch (ServiceException se) {
      throw ProtobufUtil.getRemoteException(se);
    }
  }

  這個工作完成, 如果是分散式檔案恢復模式,還需要進行這個工作,recovered.edit模式之前已經幹過了

//獲取正在恢復的meta region server
Set<ServerName> previouslyFailedMetaRSs = getPreviouselyFailedMetaServersFromZK();
if (this.distributedLogReplay && (!previouslyFailedMetaRSs.isEmpty())) {
      previouslyFailedMetaRSs.addAll(previouslyFailedServers);
      this.fileSystemManager.splitMetaLog(previouslyFailedMetaRSs);
}

分配使用者Region

  之後就是一些清理工作了,處理掉失敗的server,修改一些不正確的region的狀態,分配所有使用者的region。

    // 已經恢復了meta表,我們現在要處理掉其它失敗的server
    for (ServerName tmpServer : previouslyFailedServers) {
      this.serverManager.processDeadServer(tmpServer, true);
    }

    // 如果是failover的情況,就修復assignmentManager當中有問題的region狀態,如果是新啟動的,就分配所有的使用者region
    this.assignmentManager.joinCluster();

  分配region的工作都是由assignmentManager來完成的,在joinCluster方法中的呼叫的processDeadServersAndRegionsInTransition的最後一句呼叫的assignAllUserRegions方法,隱藏得很深。。經過分配過的region,hmaster在啟動的時候預設會沿用上一次的結果,就不再變動了,這個是由一個引數來維護的hbase.master.startup.retainassign,預設是true。分配使用者region的方法和分配meta表的過程基本是一致的。

  至此HMaster的啟動過程做的工作基本結束了。