/** * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.hbase.client; import java.io.Closeable; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.NavigableMap; import java.util.TreeMap; import java.util.TreeSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.HRegionInfo; import org.apache.hadoop.hbase.ServerName; import org.apache.hadoop.hbase.TableNotFoundException; import org.apache.hadoop.hbase.client.HConnectionManager.HConnectable; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.PairOfSameType; /** * Scanner class that contains the <code>.META.</code> table scanning logic * and uses a Retryable scanner. Provided visitors will be called * for each row. * * Although public visibility, this is not a public-facing API and may evolve in * minor releases. * * <p> Note that during concurrent region splits, the scanner might not see * META changes across rows (for parent and daughter entries) consistently. * see HBASE-5986, and {@link BlockingMetaScannerVisitor} for details. </p> */ @InterfaceAudience.Private public class MetaScanner { private static final Log LOG = LogFactory.getLog(MetaScanner.class); /** * Scans the meta table and calls a visitor on each RowResult and uses a empty * start row value as table name. * * @param configuration conf * @param visitor A custom visitor * @throws IOException e */ public static void metaScan(Configuration configuration, MetaScannerVisitor visitor) throws IOException { metaScan(configuration, visitor, null); } /** * Scans the meta table and calls a visitor on each RowResult. Uses a table * name to locate meta regions. * * @param configuration config * @param visitor visitor object * @param userTableName User table name in meta table to start scan at. Pass * null if not interested in a particular table. * @throws IOException e */ public static void metaScan(Configuration configuration, MetaScannerVisitor visitor, byte [] userTableName) throws IOException { metaScan(configuration, visitor, userTableName, null, Integer.MAX_VALUE); } /** * Scans the meta table and calls a visitor on each RowResult. Uses a table * name and a row name to locate meta regions. And it only scans at most * <code>rowLimit</code> of rows. * * @param configuration HBase configuration. * @param visitor Visitor object. * @param userTableName User table name in meta table to start scan at. Pass * null if not interested in a particular table. * @param row Name of the row at the user table. The scan will start from * the region row where the row resides. * @param rowLimit Max of processed rows. If it is less than 0, it * will be set to default value <code>Integer.MAX_VALUE</code>. * @throws IOException e */ public static void metaScan(Configuration configuration, MetaScannerVisitor visitor, byte [] userTableName, byte[] row, int rowLimit) throws IOException { metaScan(configuration, visitor, userTableName, row, rowLimit, HConstants.META_TABLE_NAME); } /** * Scans the meta table and calls a visitor on each RowResult. Uses a table * name and a row name to locate meta regions. And it only scans at most * <code>rowLimit</code> of rows. * * @param configuration HBase configuration. * @param visitor Visitor object. Closes the visitor before returning. * @param tableName User table name in meta table to start scan at. Pass * null if not interested in a particular table. * @param row Name of the row at the user table. The scan will start from * the region row where the row resides. * @param rowLimit Max of processed rows. If it is less than 0, it * will be set to default value <code>Integer.MAX_VALUE</code>. * @param metaTableName Meta table to scan, root or meta. * @throws IOException e */ public static void metaScan(Configuration configuration, final MetaScannerVisitor visitor, final byte[] tableName, final byte[] row, final int rowLimit, final byte[] metaTableName) throws IOException { try { HConnectionManager.execute(new HConnectable<Void>(configuration) { @Override public Void connect(HConnection connection) throws IOException { metaScan(conf, connection, visitor, tableName, row, rowLimit, metaTableName); return null; } }); } finally { visitor.close(); } } private static void metaScan(Configuration configuration, HConnection connection, MetaScannerVisitor visitor, byte [] tableName, byte[] row, int rowLimit, final byte [] metaTableName) throws IOException { int rowUpperLimit = rowLimit > 0 ? rowLimit: Integer.MAX_VALUE; // if row is not null, we want to use the startKey of the row's region as // the startRow for the meta scan. byte[] startRow; if (row != null) { // Scan starting at a particular row in a particular table assert tableName != null; byte[] searchRow = HRegionInfo.createRegionName(tableName, row, HConstants.NINES, false); HTable metaTable = null; try { metaTable = new HTable(configuration, HConstants.META_TABLE_NAME); Result startRowResult = metaTable.getRowOrBefore(searchRow, HConstants.CATALOG_FAMILY); if (startRowResult == null) { throw new TableNotFoundException("Cannot find row in .META. for table: " + Bytes.toString(tableName) + ", row=" + Bytes.toStringBinary(searchRow)); } HRegionInfo regionInfo = getHRegionInfo(startRowResult); if (regionInfo == null) { throw new IOException("HRegionInfo was null or empty in Meta for " + Bytes.toString(tableName) + ", row=" + Bytes.toStringBinary(searchRow)); } byte[] rowBefore = regionInfo.getStartKey(); startRow = HRegionInfo.createRegionName(tableName, rowBefore, HConstants.ZEROES, false); } finally { if (metaTable != null) { metaTable.close(); } } } else if (tableName == null || tableName.length == 0) { // Full META scan startRow = HConstants.EMPTY_START_ROW; } else { // Scan META for an entire table startRow = HRegionInfo.createRegionName( tableName, HConstants.EMPTY_START_ROW, HConstants.ZEROES, false); } // Scan over each meta region ScannerCallable callable; int rows = Math.min(rowLimit, configuration.getInt( HConstants.HBASE_META_SCANNER_CACHING, HConstants.DEFAULT_HBASE_META_SCANNER_CACHING)); do { final Scan scan = new Scan(startRow).addFamily(HConstants.CATALOG_FAMILY); if (LOG.isDebugEnabled()) { LOG.debug("Scanning " + Bytes.toString(metaTableName) + " starting at row=" + Bytes.toStringBinary(startRow) + " for max=" + rowUpperLimit + " rows using " + connection.toString()); } callable = new ScannerCallable(connection, metaTableName, scan, null); // Open scanner callable.withRetries(); int processedRows = 0; try { callable.setCaching(rows); done: do { if (processedRows >= rowUpperLimit) { break; } //we have all the rows here Result [] rrs = callable.withRetries(); if (rrs == null || rrs.length == 0 || rrs[0].size() == 0) { break; //exit completely } for (Result rr : rrs) { if (processedRows >= rowUpperLimit) { break done; } if (!visitor.processRow(rr)) break done; //exit completely processedRows++; } //here, we didn't break anywhere. Check if we have more rows } while(true); // Advance the startRow to the end key of the current region startRow = callable.getHRegionInfo().getEndKey(); } finally { // Close scanner callable.setClose(); callable.withRetries(); } } while (Bytes.compareTo(startRow, HConstants.LAST_ROW) != 0); } /** * Returns HRegionInfo object from the column * HConstants.CATALOG_FAMILY:HConstants.REGIONINFO_QUALIFIER of the catalog * table Result. * @param data a Result object from the catalog table scan * @return HRegionInfo or null */ public static HRegionInfo getHRegionInfo(Result data) { byte [] bytes = data.getValue(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER); if (bytes == null) return null; HRegionInfo info = HRegionInfo.parseFromOrNull(bytes); if (LOG.isDebugEnabled()) { LOG.debug("Current INFO from scan results = " + info); } return info; } /** * Lists all of the regions currently in META. * @param conf * @return List of all user-space regions. * @throws IOException */ public static List<HRegionInfo> listAllRegions(Configuration conf) throws IOException { return listAllRegions(conf, true); } /** * Lists all of the regions currently in META. * @param conf * @param offlined True if we are to include offlined regions, false and we'll * leave out offlined regions from returned list. * @return List of all user-space regions. * @throws IOException */ public static List<HRegionInfo> listAllRegions(Configuration conf, final boolean offlined) throws IOException { final List<HRegionInfo> regions = new ArrayList<HRegionInfo>(); MetaScannerVisitor visitor = new BlockingMetaScannerVisitor(conf) { @Override public boolean processRowInternal(Result result) throws IOException { if (result == null || result.isEmpty()) { return true; } HRegionInfo regionInfo = getHRegionInfo(result); if (regionInfo == null) { LOG.warn("Null REGIONINFO_QUALIFIER: " + result); return true; } // If region offline AND we are not to include offlined regions, return. if (regionInfo.isOffline() && !offlined) return true; regions.add(regionInfo); return true; } }; metaScan(conf, visitor); return regions; } /** * Lists all of the table regions currently in META. * @param conf * @param offlined True if we are to include offlined regions, false and we'll * leave out offlined regions from returned list. * @return Map of all user-space regions to servers * @throws IOException */ public static NavigableMap<HRegionInfo, ServerName> allTableRegions(Configuration conf, final byte [] tablename, final boolean offlined) throws IOException { final NavigableMap<HRegionInfo, ServerName> regions = new TreeMap<HRegionInfo, ServerName>(); MetaScannerVisitor visitor = new TableMetaScannerVisitor(conf, tablename) { @Override public boolean processRowInternal(Result rowResult) throws IOException { HRegionInfo info = getHRegionInfo(rowResult); ServerName serverName = HRegionInfo.getServerName(rowResult); if (!(info.isOffline() || info.isSplit())) { regions.put(new UnmodifyableHRegionInfo(info), serverName); } return true; } }; metaScan(conf, visitor, tablename); return regions; } /** * Visitor class called to process each row of the .META. table */ public interface MetaScannerVisitor extends Closeable { /** * Visitor method that accepts a RowResult and the meta region location. * Implementations can return false to stop the region's loop if it becomes * unnecessary for some reason. * * @param rowResult result * @return A boolean to know if it should continue to loop in the region * @throws IOException e */ public boolean processRow(Result rowResult) throws IOException; } public static abstract class MetaScannerVisitorBase implements MetaScannerVisitor { @Override public void close() throws IOException { } } /** * A MetaScannerVisitor that provides a consistent view of the table's * META entries during concurrent splits (see HBASE-5986 for details). This class * does not guarantee ordered traversal of meta entries, and can block until the * META entries for daughters are available during splits. */ public static abstract class BlockingMetaScannerVisitor extends MetaScannerVisitorBase { private static final int DEFAULT_BLOCKING_TIMEOUT = 10000; private Configuration conf; private TreeSet<byte[]> daughterRegions = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR); private int blockingTimeout; private HTable metaTable; public BlockingMetaScannerVisitor(Configuration conf) { this.conf = conf; this.blockingTimeout = conf.getInt(HConstants.HBASE_CLIENT_OPERATION_TIMEOUT, DEFAULT_BLOCKING_TIMEOUT); } public abstract boolean processRowInternal(Result rowResult) throws IOException; @Override public void close() throws IOException { super.close(); if (metaTable != null) { metaTable.close(); metaTable = null; } } public HTable getMetaTable() throws IOException { if (metaTable == null) { metaTable = new HTable(conf, HConstants.META_TABLE_NAME); } return metaTable; } @Override public boolean processRow(Result rowResult) throws IOException { HRegionInfo info = getHRegionInfo(rowResult); if (info == null) { return true; } if (daughterRegions.remove(info.getRegionName())) { return true; //we have already processed this row } if (info.isSplitParent()) { /* we have found a parent region which was split. We have to ensure that it's daughters are * seen by this scanner as well, so we block until they are added to the META table. Even * though we are waiting for META entries, ACID semantics in HBase indicates that this * scanner might not see the new rows. So we manually query the daughter rows */ PairOfSameType<HRegionInfo> daughters = HRegionInfo.getDaughterRegions(rowResult); HRegionInfo splitA = daughters.getFirst(); HRegionInfo splitB = daughters.getSecond(); HTable metaTable = getMetaTable(); long start = System.currentTimeMillis(); Result resultA = getRegionResultBlocking(metaTable, blockingTimeout, splitA.getRegionName()); if (resultA != null) { processRow(resultA); daughterRegions.add(splitA.getRegionName()); } else { throw new RegionOfflineException("Split daughter region " + splitA.getRegionNameAsString() + " cannot be found in META."); } long rem = blockingTimeout - (System.currentTimeMillis() - start); Result resultB = getRegionResultBlocking(metaTable, rem, splitB.getRegionName()); if (resultB != null) { processRow(resultB); daughterRegions.add(splitB.getRegionName()); } else { throw new RegionOfflineException("Split daughter region " + splitB.getRegionNameAsString() + " cannot be found in META."); } } return processRowInternal(rowResult); } private Result getRegionResultBlocking(HTable metaTable, long timeout, byte[] regionName) throws IOException { if (LOG.isDebugEnabled()) { LOG.debug("blocking until region is in META: " + Bytes.toStringBinary(regionName)); } long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start < timeout) { Get get = new Get(regionName); Result result = metaTable.get(get); HRegionInfo info = getHRegionInfo(result); if (info != null) { return result; } try { Thread.sleep(10); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); break; } } return null; } } /** * A MetaScannerVisitor for a table. Provides a consistent view of the table's * META entries during concurrent splits (see HBASE-5986 for details). This class * does not guarantee ordered traversal of meta entries, and can block until the * META entries for daughters are available during splits. */ public static abstract class TableMetaScannerVisitor extends BlockingMetaScannerVisitor { private byte[] tableName; public TableMetaScannerVisitor(Configuration conf, byte[] tableName) { super(conf); this.tableName = tableName; } @Override public final boolean processRow(Result rowResult) throws IOException { HRegionInfo info = getHRegionInfo(rowResult); if (info == null) { return true; } if (!(Bytes.equals(info.getTableName(), tableName))) { return false; } return super.processRow(rowResult); } } }