View Javadoc

1   /**
2    * Copyright 2009 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  
21  package org.apache.hadoop.hbase.client;
22  
23  import java.io.Closeable;
24  import java.io.IOException;
25  import java.util.ArrayList;
26  import java.util.List;
27  import java.util.NavigableMap;
28  import java.util.TreeMap;
29  import java.util.TreeSet;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.hadoop.conf.Configuration;
34  import org.apache.hadoop.hbase.HConstants;
35  import org.apache.hadoop.hbase.HRegionInfo;
36  import org.apache.hadoop.hbase.ServerName;
37  import org.apache.hadoop.hbase.TableNotFoundException;
38  import org.apache.hadoop.hbase.client.HConnectionManager.HConnectable;
39  import org.apache.hadoop.hbase.errorhandling.TimeoutException;
40  import org.apache.hadoop.hbase.util.Bytes;
41  import org.apache.hadoop.hbase.util.Writables;
42  
43  /**
44   * Scanner class that contains the <code>.META.</code> table scanning logic
45   * and uses a Retryable scanner. Provided visitors will be called
46   * for each row.
47   *
48   * Although public visibility, this is not a public-facing API and may evolve in
49   * minor releases.
50   *
51   * <p> Note that during concurrent region splits, the scanner might not see
52   * META changes across rows (for parent and daughter entries) consistently.
53   * see HBASE-5986, and {@link BlockingMetaScannerVisitor} for details. </p>
54   */
55  public class MetaScanner {
56    private static final Log LOG = LogFactory.getLog(MetaScanner.class);
57    /**
58     * Scans the meta table and calls a visitor on each RowResult and uses a empty
59     * start row value as table name.
60     *
61     * @param configuration conf
62     * @param visitor A custom visitor
63     * @throws IOException e
64     */
65    public static void metaScan(Configuration configuration,
66        MetaScannerVisitor visitor)
67    throws IOException {
68      metaScan(configuration, visitor, null);
69    }
70  
71    /**
72     * Scans the meta table and calls a visitor on each RowResult. Uses a table
73     * name to locate meta regions.
74     *
75     * @param configuration config
76     * @param visitor visitor object
77     * @param userTableName User table name in meta table to start scan at.  Pass
78     * null if not interested in a particular table.
79     * @throws IOException e
80     */
81    public static void metaScan(Configuration configuration,
82        MetaScannerVisitor visitor, byte [] userTableName)
83    throws IOException {
84      metaScan(configuration, visitor, userTableName, null, Integer.MAX_VALUE);
85    }
86  
87    /**
88     * Scans the meta table and calls a visitor on each RowResult. Uses a table
89     * name and a row name to locate meta regions. And it only scans at most
90     * <code>rowLimit</code> of rows.
91     *
92     * @param configuration HBase configuration.
93     * @param visitor Visitor object.
94     * @param userTableName User table name in meta table to start scan at.  Pass
95     * null if not interested in a particular table.
96     * @param row Name of the row at the user table. The scan will start from
97     * the region row where the row resides.
98     * @param rowLimit Max of processed rows. If it is less than 0, it
99     * will be set to default value <code>Integer.MAX_VALUE</code>.
100    * @throws IOException e
101    */
102   public static void metaScan(Configuration configuration,
103       MetaScannerVisitor visitor, byte [] userTableName, byte[] row,
104       int rowLimit)
105   throws IOException {
106     metaScan(configuration, visitor, userTableName, row, rowLimit,
107       HConstants.META_TABLE_NAME);
108   }
109 
110   /**
111    * Scans the meta table and calls a visitor on each RowResult. Uses a table
112    * name and a row name to locate meta regions. And it only scans at most
113    * <code>rowLimit</code> of rows.
114    *
115    * @param configuration HBase configuration.
116    * @param visitor Visitor object. Closes the visitor before returning.
117    * @param tableName User table name in meta table to start scan at.  Pass
118    * null if not interested in a particular table.
119    * @param row Name of the row at the user table. The scan will start from
120    * the region row where the row resides.
121    * @param rowLimit Max of processed rows. If it is less than 0, it
122    * will be set to default value <code>Integer.MAX_VALUE</code>.
123    * @param metaTableName Meta table to scan, root or meta.
124    * @throws IOException e
125    */
126   public static void metaScan(Configuration configuration,
127       final MetaScannerVisitor visitor, final byte[] tableName,
128       final byte[] row, final int rowLimit, final byte[] metaTableName)
129       throws IOException {
130     try {
131       HConnectionManager.execute(new HConnectable<Void>(configuration) {
132         @Override
133         public Void connect(HConnection connection) throws IOException {
134           metaScan(conf, connection, visitor, tableName, row, rowLimit,
135               metaTableName);
136           return null;
137         }
138       });
139     } finally {
140       visitor.close();
141     }
142   }
143 
144   private static void metaScan(Configuration configuration, HConnection connection,
145       MetaScannerVisitor visitor, byte [] tableName, byte[] row,
146       int rowLimit, final byte [] metaTableName)
147   throws IOException {
148     int rowUpperLimit = rowLimit > 0 ? rowLimit: Integer.MAX_VALUE;
149 
150     // if row is not null, we want to use the startKey of the row's region as
151     // the startRow for the meta scan.
152     byte[] startRow;
153     if (row != null) {
154       // Scan starting at a particular row in a particular table
155       assert tableName != null;
156       byte[] searchRow =
157         HRegionInfo.createRegionName(tableName, row, HConstants.NINES,
158           false);
159       HTable metaTable = null;
160       try {
161         metaTable = new HTable(configuration, HConstants.META_TABLE_NAME);
162         Result startRowResult = metaTable.getRowOrBefore(searchRow,
163             HConstants.CATALOG_FAMILY);
164         if (startRowResult == null) {
165           throw new TableNotFoundException("Cannot find row in .META. for table: "
166               + Bytes.toString(tableName) + ", row=" + Bytes.toStringBinary(searchRow));
167         }
168         byte[] value = startRowResult.getValue(HConstants.CATALOG_FAMILY,
169             HConstants.REGIONINFO_QUALIFIER);
170         if (value == null || value.length == 0) {
171           throw new IOException("HRegionInfo was null or empty in Meta for " +
172             Bytes.toString(tableName) + ", row=" + Bytes.toStringBinary(searchRow));
173         }
174         HRegionInfo regionInfo = Writables.getHRegionInfo(value);
175 
176         byte[] rowBefore = regionInfo.getStartKey();
177         startRow = HRegionInfo.createRegionName(tableName, rowBefore,
178             HConstants.ZEROES, false);
179       } finally {
180         if (metaTable != null) {
181           metaTable.close();
182         }
183       }
184     } else if (tableName == null || tableName.length == 0) {
185       // Full META scan
186       startRow = HConstants.EMPTY_START_ROW;
187     } else {
188       // Scan META for an entire table
189       startRow = HRegionInfo.createRegionName(
190           tableName, HConstants.EMPTY_START_ROW, HConstants.ZEROES, false);
191     }
192 
193     // Scan over each meta region
194     ScannerCallable callable;
195     int rows = Math.min(rowLimit, configuration.getInt(
196         HConstants.HBASE_META_SCANNER_CACHING,
197         HConstants.DEFAULT_HBASE_META_SCANNER_CACHING));
198     do {
199       final Scan scan = new Scan(startRow).addFamily(HConstants.CATALOG_FAMILY);
200       if (LOG.isDebugEnabled()) {
201         LOG.debug("Scanning " + Bytes.toString(metaTableName) +
202           " starting at row=" + Bytes.toStringBinary(startRow) + " for max=" +
203           rowUpperLimit + " rows using " + connection.toString());
204       }
205       callable = new ScannerCallable(connection, metaTableName, scan, null);
206       // Open scanner
207       callable.withRetries();
208 
209       int processedRows = 0;
210       try {
211         callable.setCaching(rows);
212         done: do {
213           if (processedRows >= rowUpperLimit) {
214             break;
215           }
216           //we have all the rows here
217           Result [] rrs = callable.withRetries();
218           if (rrs == null || rrs.length == 0 || rrs[0].size() == 0) {
219             break; //exit completely
220           }
221           for (Result rr : rrs) {
222             if (processedRows >= rowUpperLimit) {
223               break done;
224             }
225             if (!visitor.processRow(rr))
226               break done; //exit completely
227             processedRows++;
228           }
229           //here, we didn't break anywhere. Check if we have more rows
230         } while(true);
231         // Advance the startRow to the end key of the current region
232         startRow = callable.getHRegionInfo().getEndKey();
233       } finally {
234         // Close scanner
235         callable.setClose();
236         callable.withRetries();
237       }
238     } while (Bytes.compareTo(startRow, HConstants.LAST_ROW) != 0);
239   }
240 
241   /**
242    * Lists all of the regions currently in META.
243    * @param conf
244    * @return List of all user-space regions.
245    * @throws IOException
246    */
247   public static List<HRegionInfo> listAllRegions(Configuration conf)
248   throws IOException {
249     return listAllRegions(conf, true);
250   }
251 
252   /**
253    * Lists all of the regions currently in META.
254    * @param conf
255    * @param offlined True if we are to include offlined regions, false and we'll
256    * leave out offlined regions from returned list.
257    * @return List of all user-space regions.
258    * @throws IOException
259    */
260   public static List<HRegionInfo> listAllRegions(Configuration conf, final boolean offlined)
261   throws IOException {
262     final List<HRegionInfo> regions = new ArrayList<HRegionInfo>();
263     MetaScannerVisitor visitor = new BlockingMetaScannerVisitor(conf) {
264         @Override
265         public boolean processRowInternal(Result result) throws IOException {
266           if (result == null || result.isEmpty()) {
267             return true;
268           }
269           byte [] bytes = result.getValue(HConstants.CATALOG_FAMILY,
270             HConstants.REGIONINFO_QUALIFIER);
271           if (bytes == null) {
272             LOG.warn("Null REGIONINFO_QUALIFIER: " + result);
273             return true;
274           }
275           HRegionInfo regionInfo = Writables.getHRegionInfo(bytes);
276           // If region offline AND we are not to include offlined regions, return.
277           if (regionInfo.isOffline() && !offlined) return true;
278           regions.add(regionInfo);
279           return true;
280         }
281     };
282     metaScan(conf, visitor);
283     return regions;
284   }
285 
286   /**
287    * Lists all of the table regions currently in META.
288    * @param conf
289    * @param offlined True if we are to include offlined regions, false and we'll
290    * leave out offlined regions from returned list.
291    * @return Map of all user-space regions to servers
292    * @throws IOException
293    */
294   public static NavigableMap<HRegionInfo, ServerName> allTableRegions(Configuration conf,
295       final byte [] tablename, final boolean offlined) throws IOException {
296     final NavigableMap<HRegionInfo, ServerName> regions =
297       new TreeMap<HRegionInfo, ServerName>();
298     MetaScannerVisitor visitor = new TableMetaScannerVisitor(conf, tablename) {
299       @Override
300       public boolean processRowInternal(Result rowResult) throws IOException {
301         HRegionInfo info = Writables.getHRegionInfo(
302             rowResult.getValue(HConstants.CATALOG_FAMILY,
303                 HConstants.REGIONINFO_QUALIFIER));
304         byte [] value = rowResult.getValue(HConstants.CATALOG_FAMILY,
305           HConstants.SERVER_QUALIFIER);
306         String hostAndPort = null;
307         if (value != null && value.length > 0) {
308           hostAndPort = Bytes.toString(value);
309         }
310         value = rowResult.getValue(HConstants.CATALOG_FAMILY,
311           HConstants.STARTCODE_QUALIFIER);
312         long startcode = -1L;
313         if (value != null && value.length > 0) startcode = Bytes.toLong(value);
314         if (!(info.isOffline() || info.isSplit())) {
315           ServerName sn = null;
316           if (hostAndPort != null && hostAndPort.length() > 0) {
317             sn = new ServerName(hostAndPort, startcode);
318           }
319           regions.put(new UnmodifyableHRegionInfo(info), sn);
320         }
321         return true;
322       }
323     };
324     metaScan(conf, visitor, tablename);
325     return regions;
326   }
327 
328   /**
329    * Visitor class called to process each row of the .META. table
330    */
331   public interface MetaScannerVisitor extends Closeable {
332     /**
333      * Visitor method that accepts a RowResult and the meta region location.
334      * Implementations can return false to stop the region's loop if it becomes
335      * unnecessary for some reason.
336      *
337      * @param rowResult result
338      * @return A boolean to know if it should continue to loop in the region
339      * @throws IOException e
340      */
341     public boolean processRow(Result rowResult) throws IOException;
342   }
343 
344   public static abstract class MetaScannerVisitorBase implements MetaScannerVisitor {
345     @Override
346     public void close() throws IOException {
347     }
348   }
349 
350   /**
351    * A MetaScannerVisitor that provides a consistent view of the table's
352    * META entries during concurrent splits (see HBASE-5986 for details). This class
353    * does not guarantee ordered traversal of meta entries, and can block until the
354    * META entries for daughters are available during splits.
355    */
356   public static abstract class BlockingMetaScannerVisitor
357     extends MetaScannerVisitorBase {
358 
359     private static final int DEFAULT_BLOCKING_TIMEOUT = 10000;
360     private Configuration conf;
361     private TreeSet<byte[]> daughterRegions = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR);
362     private int blockingTimeout;
363     private HTable metaTable;
364 
365     public BlockingMetaScannerVisitor(Configuration conf) {
366       this.conf = conf;
367       this.blockingTimeout = conf.getInt(HConstants.HBASE_CLIENT_OPERATION_TIMEOUT,
368           DEFAULT_BLOCKING_TIMEOUT);
369     }
370 
371     public abstract boolean processRowInternal(Result rowResult) throws IOException;
372 
373     @Override
374     public void close() throws IOException {
375       super.close();
376       if (metaTable != null) {
377         metaTable.close();
378         metaTable = null;
379       }
380     }
381 
382     public HTable getMetaTable() throws IOException {
383       if (metaTable == null) {
384         metaTable = new HTable(conf, HConstants.META_TABLE_NAME);
385       }
386       return metaTable;
387     }
388 
389     @Override
390     public boolean processRow(Result rowResult) throws IOException {
391       HRegionInfo info = Writables.getHRegionInfoOrNull(
392           rowResult.getValue(HConstants.CATALOG_FAMILY,
393               HConstants.REGIONINFO_QUALIFIER));
394       if (info == null) {
395         return true;
396       }
397 
398       if (daughterRegions.remove(info.getRegionName())) {
399         return true; //we have already processed this row
400       }
401 
402       if (info.isSplitParent()) {
403         /* we have found a parent region which was split. We have to ensure that it's daughters are
404          * seen by this scanner as well, so we block until they are added to the META table. Even
405          * though we are waiting for META entries, ACID semantics in HBase indicates that this
406          * scanner might not see the new rows. So we manually query the daughter rows */
407         HRegionInfo splitA = Writables.getHRegionInfoOrNull(rowResult.getValue(HConstants.CATALOG_FAMILY,
408             HConstants.SPLITA_QUALIFIER));
409         HRegionInfo splitB = Writables.getHRegionInfoOrNull(rowResult.getValue(HConstants.CATALOG_FAMILY,
410             HConstants.SPLITB_QUALIFIER));
411 
412         HTable metaTable = getMetaTable();
413         long start = System.currentTimeMillis();
414         if (splitA != null) {
415           try {
416             Result resultA = getRegionResultBlocking(metaTable, blockingTimeout,
417               info.getRegionName(), splitA.getRegionName());
418             if (resultA != null) {
419               processRow(resultA);
420               daughterRegions.add(splitA.getRegionName());
421             }
422             // else parent is gone, so skip this daughter
423           } catch (TimeoutException e) {
424             throw new RegionOfflineException("Split daughter region " +
425                 splitA.getRegionNameAsString() + " cannot be found in META. Parent:" +
426                 info.getRegionNameAsString());
427           }
428         }
429         long rem = blockingTimeout - (System.currentTimeMillis() - start);
430 
431         if (splitB != null) {
432           try {
433             Result resultB = getRegionResultBlocking(metaTable, rem,
434               info.getRegionName(), splitB.getRegionName());
435             if (resultB != null) {
436               processRow(resultB);
437               daughterRegions.add(splitB.getRegionName());
438             }
439             // else parent is gone, so skip this daughter
440           } catch (TimeoutException e) {
441             throw new RegionOfflineException("Split daughter region " +
442                 splitB.getRegionNameAsString() + " cannot be found in META. Parent:" +
443                 info.getRegionNameAsString());
444           }
445         }
446       }
447 
448       return processRowInternal(rowResult);
449     }
450 
451     /**
452      * Returns region Result by querying the META table for regionName. It will block until
453      * the region is found in META. It will also check for parent in META to make sure that
454      * if parent is deleted, we no longer have to wait, and should continue (HBASE-8590)
455      * @return Result object is daughter is found, or null if parent is gone from META
456      * @throws TimeoutException if timeout is reached
457      */
458     private Result getRegionResultBlocking(HTable metaTable, long timeout, byte[] parentRegionName, byte[] regionName)
459         throws IOException, TimeoutException {
460       boolean logged = false;
461       long start = System.currentTimeMillis();
462       while (System.currentTimeMillis() - start < timeout) {
463         Get get = new Get(regionName);
464         Result result = metaTable.get(get);
465         HRegionInfo info = Writables.getHRegionInfoOrNull(
466             result.getValue(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER));
467         if (info != null) {
468           return result;
469         }
470 
471         // check whether parent is still there, if not it means we do not need to wait
472         Get parentGet = new Get(parentRegionName);
473         Result parentResult = metaTable.get(parentGet);
474         HRegionInfo parentInfo = Writables.getHRegionInfoOrNull(
475             parentResult.getValue(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER));
476         if (parentInfo == null) {
477           // this means that parent is no more (catalog janitor or somebody else deleted it)
478           return null;
479         }
480 
481         try {
482           if (!logged) {
483             if (LOG.isDebugEnabled()) {
484               LOG.debug("blocking until region is in META: " + Bytes.toStringBinary(regionName));
485             }
486             logged = true;
487           }
488           Thread.sleep(10);
489         } catch (InterruptedException ex) {
490           Thread.currentThread().interrupt();
491           break;
492         }
493       }
494       throw new TimeoutException("getRegionResultBlocking", start, System.currentTimeMillis(),
495         timeout);
496     }
497   }
498 
499   /**
500    * A MetaScannerVisitor for a table. Provides a consistent view of the table's
501    * META entries during concurrent splits (see HBASE-5986 for details). This class
502    * does not guarantee ordered traversal of meta entries, and can block until the
503    * META entries for daughters are available during splits.
504    */
505   public static abstract class TableMetaScannerVisitor extends BlockingMetaScannerVisitor {
506     private byte[] tableName;
507 
508     public TableMetaScannerVisitor(Configuration conf, byte[] tableName) {
509       super(conf);
510       this.tableName = tableName;
511     }
512 
513     @Override
514     public final boolean processRow(Result rowResult) throws IOException {
515       HRegionInfo info = Writables.getHRegionInfoOrNull(
516           rowResult.getValue(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER));
517       if (info == null) {
518         return true;
519       }
520       if (!(Bytes.equals(info.getTableName(), tableName))) {
521         return false;
522       }
523       return super.processRow(rowResult);
524     }
525 
526   }
527 }