client-related source code is located in the module hbase-client.

When client tries to perform an operation, like Put/Delete/Get, it need to know which RegionServer the data lies on. This location is implemented by the method locateRegion in ConnectionImpletation.java which is the main implementation of Connection and ClusterConnection interfaces.

ConnectionImpletation also encapsulates connection to ZooKeeper and RegionServers.

There are a few overloaded locateRegion methods in ConnectionImpletation. The one which really executes actual work is:

  public RegionLocations locateRegion(final TableName tableName,
    final byte [] row, boolean useCache, boolean retry, int replicaId)
  throws IOException {
    if (this.closed) {
      throw new DoNotRetryIOException(toString() + " closed");
    }
    if (tableName== null || tableName.getName().length == 0) {
      throw new IllegalArgumentException(
          "table name cannot be null or zero length");
    }
    if (tableName.equals(TableName.META_TABLE_NAME)) {
      return locateMeta(tableName, useCache, replicaId);
    } else {
      // Region not in the cache - have to go to the meta RS
      return locateRegionInMeta(tableName, row, useCache, retry, replicaId);
    }
  }

The following list the brief steps of this method:

  1. ensure the connection is open, and throw a DoNotRetryIOException if closed.
  2. check whether tableName is valid.
  3. jump to locateMeta method if tableName is equal to META.
  4. or, jump to locateRegionInMeta.

Then let’s go to the detail of locateMeta method which is also lated in ConnectionImpletation.

  private RegionLocations locateMeta(final TableName tableName,
      boolean useCache, int replicaId) throws IOException {
    // HBASE-10785: We cache the location of the META itself, so that we are not overloading
    // zookeeper with one request for every region lookup. We cache the META with empty row
    // key in MetaCache.
    byte[] metaCacheKey = HConstants.EMPTY_START_ROW; // use byte[0] as the row for meta
    RegionLocations locations = null;
    if (useCache) {
      locations = getCachedLocation(tableName, metaCacheKey);
      if (locations != null && locations.getRegionLocation(replicaId) != null) {
        return locations;
      }
    }

    // only one thread should do the lookup.
    synchronized (metaRegionLock) {
      // Check the cache again for a hit in case some other thread made the
      // same query while we were waiting on the lock.
      if (useCache) {
        locations = getCachedLocation(tableName, metaCacheKey);
        if (locations != null && locations.getRegionLocation(replicaId) != null) {
          return locations;
        }
      }

      // Look up from zookeeper
      locations = this.registry.getMetaRegionLocation();
      if (locations != null) {
        cacheLocation(tableName, locations);
      }
    }
    return locations;
  }

As the comment said, the location of META is always cached. The steps to get meta location:

  1. if useCache, get cached location for META table. This would ask for help from MetaCache class, which is a cache implementation for region locations from meta.
  2. look up from ZooKeeper.
  3. cache the location if found.

step 2 turns to getMetaRegionLocation method in ZooKeeperRegistry, A cluster registry that stores to zookeeper. Here is its code:

  public RegionLocations getMetaRegionLocation() throws IOException {
    ZooKeeperKeepAliveConnection zkw = hci.getKeepAliveZooKeeperWatcher();
    try {
      if (LOG.isTraceEnabled()) {
        LOG.trace("Looking up meta region location in ZK," + " connection=" + this);
      }
      List<ServerName> servers = new MetaTableLocator().blockUntilAvailable(zkw, hci.rpcTimeout,
          hci.getConfiguration());
      if (LOG.isTraceEnabled()) {
        if (servers == null) {
          LOG.trace("Looked up meta region location, connection=" + this +
            "; servers = null");
        } else {
          StringBuilder str = new StringBuilder();
          for (ServerName s : servers) {
            str.append(s.toString());
            str.append(" ");
          }
          LOG.trace("Looked up meta region location, connection=" + this +
            "; servers = " + str.toString());
        }
      }
      if (servers == null) return null;
      HRegionLocation[] locs = new HRegionLocation[servers.size()];
      int i = 0;
      for (ServerName server : servers) {
        HRegionInfo h = RegionReplicaUtil.getRegionInfoForReplica(
                HRegionInfo.FIRST_META_REGIONINFO, i);
        if (server == null) locs[i++] = null;
        else locs[i++] = new HRegionLocation(h, server, 0);
      }
      return new RegionLocations(locs);
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      return null;
    } finally {
      zkw.close();
    }
  }
  1. get server list from MetaTableLocator.
  2. constructs the region location list with RegionLocations from RegionLocations class.

Next, let’s see how to locate region of user table locateRegionInMeta(), which would search the hbase:meta table for the HRegionLocation info that contains the table and row your are seeking:

  private RegionLocations locateRegionInMeta(TableName tableName, byte[] row,
                 boolean useCache, boolean retry, int replicaId) throws IOException {

    // If we are supposed to be using the cache, look in the cache to see if
    // we already have the region.
    if (useCache) {
      RegionLocations locations = getCachedLocation(tableName, row);
      if (locations != null && locations.getRegionLocation(replicaId) != null) {
        return locations;
      }
    }

    // build the key of the meta region we should be looking for.
    // the extra 9's on the end are necessary to allow "exact" matches
    // without knowing the precise region names.
    byte[] metaKey = HRegionInfo.createRegionName(tableName, row, HConstants.NINES, false);

    Scan s = new Scan();
    s.setReversed(true);
    s.withStartRow(metaKey);
    s.addFamily(HConstants.CATALOG_FAMILY);
    s.setOneRowLimit();
    if (this.useMetaReplicas) {
      s.setConsistency(Consistency.TIMELINE);
    }

    int maxAttempts = (retry ? numTries : 1);

    for (int tries = 0; true; tries++) {
      if (tries >= maxAttempts) {
        throw new NoServerForRegionException("Unable to find region for "
            + Bytes.toStringBinary(row) + " in " + tableName +
            " after " + tries + " tries.");
      }
      if (useCache) {
        RegionLocations locations = getCachedLocation(tableName, row);
        if (locations != null && locations.getRegionLocation(replicaId) != null) {
          return locations;
        }
      } else {
        // If we are not supposed to be using the cache, delete any existing cached location
        // so it won't interfere.
        // We are only supposed to clean the cache for the specific replicaId
        metaCache.clearCache(tableName, row, replicaId);
      }

      // Query the meta region
      long pauseBase = this.pause;
      try {
        Result regionInfoRow = null;
        s.resetMvccReadPoint();
        try (ReversedClientScanner rcs =
            new ReversedClientScanner(conf, s, TableName.META_TABLE_NAME, this, rpcCallerFactory,
                rpcControllerFactory, getMetaLookupPool(), metaReplicaCallTimeoutScanInMicroSecond)) {
          regionInfoRow = rcs.next();
        }

        if (regionInfoRow == null) {
          throw new TableNotFoundException(tableName);
        }
        // convert the row result into the HRegionLocation we need!
        RegionLocations locations = MetaTableAccessor.getRegionLocations(regionInfoRow);
        if (locations == null || locations.getRegionLocation(replicaId) == null) {
          throw new IOException("HRegionInfo was null in " +
            tableName + ", row=" + regionInfoRow);
        }
        HRegionInfo regionInfo = locations.getRegionLocation(replicaId).getRegionInfo();
        if (regionInfo == null) {
          throw new IOException("HRegionInfo was null or empty in " +
            TableName.META_TABLE_NAME + ", row=" + regionInfoRow);
        }

        // possible we got a region of a different table...
        if (!regionInfo.getTable().equals(tableName)) {
          throw new TableNotFoundException(
            "Region of '" + regionInfo.getRegionNameAsString() + "' is expected in the table of '" + tableName + "', " +
            "but hbase:meta says it is in the table of '" + regionInfo.getTable() + "'. " +
            "hbase:meta might be damaged.");
        }
        if (regionInfo.isSplit()) {
          throw new RegionOfflineException("the only available region for" +
            " the required row is a split parent," +
            " the daughters should be online soon: " +
            regionInfo.getRegionNameAsString());
        }
        if (regionInfo.isOffline()) {
          throw new RegionOfflineException("the region is offline, could" +
            " be caused by a disable table call: " +
            regionInfo.getRegionNameAsString());
        }

        ServerName serverName = locations.getRegionLocation(replicaId).getServerName();
        if (serverName == null) {
          throw new NoServerForRegionException("No server address listed " +
            "in " + TableName.META_TABLE_NAME + " for region " +
            regionInfo.getRegionNameAsString() + " containing row " +
            Bytes.toStringBinary(row));
        }

        if (isDeadServer(serverName)){
          throw new RegionServerStoppedException("hbase:meta says the region "+
              regionInfo.getRegionNameAsString()+" is managed by the server " + serverName +
              ", but it is dead.");
        }
        // Instantiate the location
        cacheLocation(tableName, locations);
        return locations;
      } catch (TableNotFoundException e) {
        // if we got this error, probably means the table just plain doesn't
        // exist. rethrow the error immediately. this should always be coming
        // from the HTable constructor.
        throw e;
      } catch (IOException e) {
        ExceptionUtil.rethrowIfInterrupt(e);
        if (e instanceof RemoteException) {
          e = ((RemoteException)e).unwrapRemoteException();
        }
        if (e instanceof CallQueueTooBigException) {
          // Give a special check on CallQueueTooBigException, see #HBASE-17114
          pauseBase = this.pauseForCQTBE;
        }
        if (tries < maxAttempts - 1) {
          if (LOG.isDebugEnabled()) {
            LOG.debug("locateRegionInMeta parentTable=" +
                TableName.META_TABLE_NAME + ", metaLocation=" +
              ", attempt=" + tries + " of " +
              maxAttempts + " failed; retrying after sleep of " +
              ConnectionUtils.getPauseTime(pauseBase, tries) + " because: " + e.getMessage());
          }
        } else {
          throw e;
        }
        // Only relocate the parent region if necessary
        if(!(e instanceof RegionOfflineException ||
            e instanceof NoServerForRegionException)) {
          relocateRegion(TableName.META_TABLE_NAME, metaKey, replicaId);
        }
      }
      try{
        Thread.sleep(ConnectionUtils.getPauseTime(pauseBase, tries));
      } catch (InterruptedException e) {
        throw new InterruptedIOException("Giving up trying to location region in " +
          "meta: thread is interrupted.");
      }
    }
  }
  1. if using the cache, look in the cache to see if already have the region.
  2. construct a Scan object with the given tableName and row.
  3. get the first result of Scan, do some validity check.