View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.client;
19  
20  import static org.apache.hadoop.hbase.client.ConnectionUtils.createScanResultCache;
21  
22  import com.google.common.annotations.VisibleForTesting;
23  
24  import java.io.IOException;
25  import java.io.InterruptedIOException;
26  import java.util.LinkedList;
27  import java.util.concurrent.ExecutorService;
28  
29  import org.apache.commons.lang.mutable.MutableBoolean;
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.hadoop.conf.Configuration;
33  import org.apache.hadoop.hbase.DoNotRetryIOException;
34  import org.apache.hadoop.hbase.HBaseConfiguration;
35  import org.apache.hadoop.hbase.HConstants;
36  import org.apache.hadoop.hbase.HRegionInfo;
37  import org.apache.hadoop.hbase.NotServingRegionException;
38  import org.apache.hadoop.hbase.TableName;
39  import org.apache.hadoop.hbase.UnknownScannerException;
40  import org.apache.hadoop.hbase.classification.InterfaceAudience;
41  import org.apache.hadoop.hbase.client.ScannerCallable.MoreResults;
42  import org.apache.hadoop.hbase.exceptions.OutOfOrderScannerNextException;
43  import org.apache.hadoop.hbase.exceptions.ScannerResetException;
44  import org.apache.hadoop.hbase.ipc.RpcControllerFactory;
45  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
46  import org.apache.hadoop.hbase.regionserver.RegionServerStoppedException;
47  import org.apache.hadoop.hbase.util.Bytes;
48  
49  /**
50   * Implements the scanner interface for the HBase client. If there are multiple regions in a table,
51   * this scanner will iterate through them all.
52   */
53  @InterfaceAudience.Private
54  public abstract class ClientScanner extends AbstractClientScanner {
55    private static final Log LOG = LogFactory.getLog(ClientScanner.class);
56  
57    protected final Scan scan;
58    protected boolean closed = false;
59    // Current region scanner is against. Gets cleared if current region goes
60    // wonky: e.g. if it splits on us.
61    protected HRegionInfo currentRegion = null;
62    protected ScannerCallableWithReplicas callable = null;
63    protected final LinkedList<Result> cache = new LinkedList<Result>();
64    private final ScanResultCache scanResultCache;
65    protected final int caching;
66    protected long lastNext;
67    // Keep lastResult returned successfully in case we have to reset scanner.
68    protected Result lastResult = null;
69    protected final long maxScannerResultSize;
70    private final ClusterConnection connection;
71    private final TableName tableName;
72    protected final int scannerTimeout;
73    protected boolean scanMetricsPublished = false;
74    protected RpcRetryingCaller<Result[]> caller;
75    protected RpcControllerFactory rpcControllerFactory;
76    protected Configuration conf;
77    // The timeout on the primary. Applicable if there are multiple replicas for a region
78    // In that case, we will only wait for this much timeout on the primary before going
79    // to the replicas and trying the same scan. Note that the retries will still happen
80    // on each replica and the first successful results will be taken. A timeout of 0 is
81    // disallowed.
82    protected final int primaryOperationTimeout;
83    private int retries;
84    protected final ExecutorService pool;
85  
86    /**
87     * Create a new ClientScanner for the specified table Note that the passed {@link Scan}'s start
88     * row maybe changed changed.
89     * @param conf The {@link Configuration} to use.
90     * @param scan {@link Scan} to use in this scanner
91     * @param tableName The table that we wish to scan
92     * @param connection Connection identifying the cluster
93     * @throws IOException
94     */
95    public ClientScanner(final Configuration conf, final Scan scan, final TableName tableName,
96        ClusterConnection connection, RpcRetryingCallerFactory rpcFactory,
97        RpcControllerFactory controllerFactory, ExecutorService pool, int primaryOperationTimeout)
98        throws IOException {
99      if (LOG.isTraceEnabled()) {
100       LOG.trace(
101         "Scan table=" + tableName + ", startRow=" + Bytes.toStringBinary(scan.getStartRow()));
102     }
103     this.scan = scan;
104     this.tableName = tableName;
105     this.lastNext = System.currentTimeMillis();
106     this.connection = connection;
107     this.pool = pool;
108     this.primaryOperationTimeout = primaryOperationTimeout;
109     this.retries = conf.getInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER,
110       HConstants.DEFAULT_HBASE_CLIENT_RETRIES_NUMBER);
111     if (scan.getMaxResultSize() > 0) {
112       this.maxScannerResultSize = scan.getMaxResultSize();
113     } else {
114       this.maxScannerResultSize = conf.getLong(HConstants.HBASE_CLIENT_SCANNER_MAX_RESULT_SIZE_KEY,
115         HConstants.DEFAULT_HBASE_CLIENT_SCANNER_MAX_RESULT_SIZE);
116     }
117     this.scannerTimeout =
118         HBaseConfiguration.getInt(conf, HConstants.HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD,
119           HConstants.HBASE_REGIONSERVER_LEASE_PERIOD_KEY,
120           HConstants.DEFAULT_HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD);
121 
122     // check if application wants to collect scan metrics
123     initScanMetrics(scan);
124 
125     // Use the caching from the Scan. If not set, use the default cache setting for this table.
126     if (this.scan.getCaching() > 0) {
127       this.caching = this.scan.getCaching();
128     } else {
129       this.caching = conf.getInt(HConstants.HBASE_CLIENT_SCANNER_CACHING,
130         HConstants.DEFAULT_HBASE_CLIENT_SCANNER_CACHING);
131     }
132 
133     this.caller = rpcFactory.<Result[]> newCaller();
134     this.rpcControllerFactory = controllerFactory;
135 
136     this.conf = conf;
137     this.scanResultCache = createScanResultCache(scan, cache);
138   }
139 
140   protected ClusterConnection getConnection() {
141     return this.connection;
142   }
143 
144   /**
145    * @return Table name
146    * @deprecated As of release 0.96
147    *             (<a href="https://issues.apache.org/jira/browse/HBASE-9508">HBASE-9508</a>). This
148    *             will be removed in HBase 2.0.0. Use {@link #getTable()}.
149    */
150   @Deprecated
151   protected byte[] getTableName() {
152     return this.tableName.getName();
153   }
154 
155   protected TableName getTable() {
156     return this.tableName;
157   }
158 
159   protected int getRetries() {
160     return this.retries;
161   }
162 
163   protected int getScannerTimeout() {
164     return this.scannerTimeout;
165   }
166 
167   protected Configuration getConf() {
168     return this.conf;
169   }
170 
171   protected Scan getScan() {
172     return scan;
173   }
174 
175   protected ExecutorService getPool() {
176     return pool;
177   }
178 
179   protected int getPrimaryOperationTimeout() {
180     return primaryOperationTimeout;
181   }
182 
183   protected int getCaching() {
184     return caching;
185   }
186 
187   protected long getTimestamp() {
188     return lastNext;
189   }
190 
191   @VisibleForTesting
192   protected long getMaxResultSize() {
193     return maxScannerResultSize;
194   }
195 
196   private void closeScanner() throws IOException {
197     if (this.callable != null) {
198       this.callable.setClose();
199       call(callable, caller, scannerTimeout, false);
200       this.callable = null;
201     }
202   }
203 
204   /**
205    * Will be called in moveToNextRegion when currentRegion is null. Abstract because for normal
206    * scan, we will start next scan from the endKey of the currentRegion, and for reversed scan, we
207    * will start next scan from the startKey of the currentRegion.
208    * @return {@code false} if we have reached the stop row. Otherwise {@code true}.
209    */
210   protected abstract boolean setNewStartKey();
211 
212   /**
213    * Will be called in moveToNextRegion to create ScannerCallable. Abstract because for reversed
214    * scan we need to create a ReversedScannerCallable.
215    */
216   protected abstract ScannerCallable createScannerCallable();
217 
218   /**
219    * Close the previous scanner and create a new ScannerCallable for the next scanner.
220    * <p>
221    * Marked as protected only because TestClientScanner need to override this method.
222    * @return false if we should terminate the scan. Otherwise
223    */
224   @VisibleForTesting
225   protected boolean moveToNextRegion() {
226     // Close the previous scanner if it's open
227     try {
228       closeScanner();
229     } catch (IOException e) {
230       // not a big deal continue
231       if (LOG.isDebugEnabled()) {
232         LOG.debug("close scanner for " + currentRegion + " failed", e);
233       }
234     }
235     if (currentRegion != null) {
236       if (!setNewStartKey()) {
237         return false;
238       }
239       scan.resetMvccReadPoint();
240       if (LOG.isTraceEnabled()) {
241         LOG.trace("Finished " + this.currentRegion);
242       }
243     }
244     if (LOG.isDebugEnabled() && this.currentRegion != null) {
245       // Only worth logging if NOT first region in scan.
246       LOG.debug(
247         "Advancing internal scanner to startKey at '" + Bytes.toStringBinary(scan.getStartRow()) +
248             "', " + (scan.includeStartRow() ? "inclusive" : "exclusive"));
249     }
250     // clear the current region, we will set a new value to it after the first call of the new
251     // callable.
252     this.currentRegion = null;
253     this.callable =
254         new ScannerCallableWithReplicas(getTable(), getConnection(), createScannerCallable(), pool,
255             primaryOperationTimeout, scan, getRetries(), scannerTimeout, caching, conf, caller);
256     this.callable.setCaching(this.caching);
257     if (this.scanMetrics != null) {
258       this.scanMetrics.countOfRegions.incrementAndGet();
259     }
260     return true;
261   }
262 
263   @VisibleForTesting
264   boolean isAnyRPCcancelled() {
265     return callable.isAnyRPCcancelled();
266   }
267 
268   private Result[] call(ScannerCallableWithReplicas callable, RpcRetryingCaller<Result[]> caller,
269       int scannerTimeout, boolean updateCurrentRegion) throws IOException {
270     if (Thread.interrupted()) {
271       throw new InterruptedIOException();
272     }
273     // callWithoutRetries is at this layer. Within the ScannerCallableWithReplicas,
274     // we do a callWithRetries
275     Result[] rrs = caller.callWithoutRetries(callable, scannerTimeout);
276     if (currentRegion == null && updateCurrentRegion) {
277       currentRegion = callable.getHRegionInfo();
278     }
279     return rrs;
280   }
281 
282   /**
283    * Publish the scan metrics. For now, we use scan.setAttribute to pass the metrics back to the
284    * application or TableInputFormat.Later, we could push it to other systems. We don't use metrics
285    * framework because it doesn't support multi-instances of the same metrics on the same machine;
286    * for scan/map reduce scenarios, we will have multiple scans running at the same time. By
287    * default, scan metrics are disabled; if the application wants to collect them, this behavior can
288    * be turned on by calling calling {@link Scan#setScanMetricsEnabled(boolean)}
289    */
290   protected void writeScanMetrics() {
291     if (this.scanMetrics == null || scanMetricsPublished) {
292       return;
293     }
294     // Publish ScanMetrics to the Scan Object.
295     // As we have claimed in the comment of Scan.getScanMetrics, this relies on that user will not
296     // call ResultScanner.getScanMetrics and reset the ScanMetrics. Otherwise the metrics published
297     // to Scan will be messed up.
298     scan.setAttribute(Scan.SCAN_ATTRIBUTES_METRICS_DATA,
299       ProtobufUtil.toScanMetrics(scanMetrics, false).toByteArray());
300     scanMetricsPublished = true;
301   }
302 
303   @Override
304   public Result next() throws IOException {
305     // If the scanner is closed and there's nothing left in the cache, next is a no-op.
306     if (cache.size() == 0 && this.closed) {
307       return null;
308     }
309     if (cache.size() == 0) {
310       loadCache();
311     }
312 
313     if (cache.size() > 0) {
314       return cache.poll();
315     }
316 
317     // if we exhausted this scanner before calling close, write out the scan metrics
318     writeScanMetrics();
319     return null;
320   }
321 
322   @VisibleForTesting
323   public int getCacheSize() {
324     return cache != null ? cache.size() : 0;
325   }
326 
327   private boolean scanExhausted(Result[] values) {
328     return callable.moreResultsForScan() == MoreResults.NO;
329   }
330 
331   private boolean regionExhausted(Result[] values) {
332     // 1. Not a heartbeat message and we get nothing, this means the region is exhausted. And in the
333     // old time we always return empty result for a open scanner operation so we add a check here to
334     // keep compatible with the old logic. Should remove the isOpenScanner in the future.
335     // 2. Server tells us that it has no more results for this region.
336     return (values.length == 0 && !callable.isHeartbeatMessage()) ||
337         callable.moreResultsInRegion() == MoreResults.NO;
338   }
339 
340   private void closeScannerIfExhausted(boolean exhausted) throws IOException {
341     if (exhausted) {
342       closeScanner();
343     }
344   }
345 
346   private void handleScanError(DoNotRetryIOException e,
347       MutableBoolean retryAfterOutOfOrderException, int retriesLeft) throws DoNotRetryIOException {
348     // An exception was thrown which makes any partial results that we were collecting
349     // invalid. The scanner will need to be reset to the beginning of a row.
350     scanResultCache.clear();
351 
352     // Unfortunately, DNRIOE is used in two different semantics.
353     // (1) The first is to close the client scanner and bubble up the exception all the way
354     // to the application. This is preferred when the exception is really un-recoverable
355     // (like CorruptHFileException, etc). Plain DoNotRetryIOException also falls into this
356     // bucket usually.
357     // (2) Second semantics is to close the current region scanner only, but continue the
358     // client scanner by overriding the exception. This is usually UnknownScannerException,
359     // OutOfOrderScannerNextException, etc where the region scanner has to be closed, but the
360     // application-level ClientScanner has to continue without bubbling up the exception to
361     // the client. See RSRpcServices to see how it throws DNRIOE's.
362     // See also: HBASE-16604, HBASE-17187
363 
364     // If exception is any but the list below throw it back to the client; else setup
365     // the scanner and retry.
366     Throwable cause = e.getCause();
367     if ((cause != null && cause instanceof NotServingRegionException) ||
368         (cause != null && cause instanceof RegionServerStoppedException) ||
369         e instanceof OutOfOrderScannerNextException || e instanceof UnknownScannerException ||
370         e instanceof ScannerResetException) {
371       // Pass. It is easier writing the if loop test as list of what is allowed rather than
372       // as a list of what is not allowed... so if in here, it means we do not throw.
373       if (retriesLeft <= 0) {
374         throw e; // no more retries
375       }
376     } else {
377       throw e;
378     }
379 
380     // Else, its signal from depths of ScannerCallable that we need to reset the scanner.
381     if (this.lastResult != null) {
382       // The region has moved. We need to open a brand new scanner at the new location.
383       // Reset the startRow to the row we've seen last so that the new scanner starts at
384       // the correct row. Otherwise we may see previously returned rows again.
385       // If the lastRow is not partial, then we should start from the next row. As now we can
386       // exclude the start row, the logic here is the same for both normal scan and reversed scan.
387       // If lastResult is partial then include it, otherwise exclude it.
388       scan.withStartRow(lastResult.getRow(), lastResult.mayHaveMoreCellsInRow());
389     }
390     if (e instanceof OutOfOrderScannerNextException) {
391       if (retryAfterOutOfOrderException.isTrue()) {
392         retryAfterOutOfOrderException.setValue(false);
393       } else {
394         // TODO: Why wrap this in a DNRIOE when it already is a DNRIOE?
395         throw new DoNotRetryIOException(
396             "Failed after retry of OutOfOrderScannerNextException: was there a rpc timeout?", e);
397       }
398     }
399     // Clear region.
400     this.currentRegion = null;
401     // Set this to zero so we don't try and do an rpc and close on remote server when
402     // the exception we got was UnknownScanner or the Server is going down.
403     callable = null;
404   }
405 
406   /**
407    * Contact the servers to load more {@link Result}s in the cache.
408    */
409   protected void loadCache() throws IOException {
410     // check if scanner was closed during previous prefetch
411     if (closed) {
412       return;
413     }
414     long remainingResultSize = maxScannerResultSize;
415     int countdown = this.caching;
416     // This is possible if we just stopped at the boundary of a region in the previous call.
417     if (callable == null) {
418       if (!moveToNextRegion()) {
419         return;
420       }
421     }
422     // This flag is set when we want to skip the result returned. We do
423     // this when we reset scanner because it split under us.
424     MutableBoolean retryAfterOutOfOrderException = new MutableBoolean(true);
425     // Even if we are retrying due to UnknownScannerException, ScannerResetException, etc. we should
426     // make sure that we are not retrying indefinitely.
427     int retriesLeft = getRetries();
428     for (;;) {
429       Result[] values;
430       try {
431         // Server returns a null values if scanning is to stop. Else,
432         // returns an empty array if scanning is to go on and we've just
433         // exhausted current region.
434         // now we will also fetch data when openScanner, so do not make a next call again if values
435         // is already non-null.
436         values = call(callable, caller, scannerTimeout, true);
437         // When the replica switch happens, we need to do certain operations again.
438         // The callable will openScanner with the right startkey but we need to pick up
439         // from there. Bypass the rest of the loop and let the catch-up happen in the beginning
440         // of the loop as it happens for the cases where we see exceptions.
441         if (callable.switchedToADifferentReplica()) {
442           // Any accumulated partial results are no longer valid since the callable will
443           // openScanner with the correct startkey and we must pick up from there
444           scanResultCache.clear();
445           this.currentRegion = callable.getHRegionInfo();
446         }
447         retryAfterOutOfOrderException.setValue(true);
448       } catch (DoNotRetryIOException e) {
449         handleScanError(e, retryAfterOutOfOrderException, retriesLeft--);
450         // reopen the scanner
451         if (!moveToNextRegion()) {
452           break;
453         }
454         continue;
455       }
456       long currentTime = System.currentTimeMillis();
457       if (this.scanMetrics != null) {
458         this.scanMetrics.sumOfMillisSecBetweenNexts.addAndGet(currentTime - lastNext);
459       }
460       lastNext = currentTime;
461       // Groom the array of Results that we received back from the server before adding that
462       // Results to the scanner's cache. If partial results are not allowed to be seen by the
463       // caller, all book keeping will be performed within this method.
464       int numberOfCompleteRowsBefore = scanResultCache.numberOfCompleteRows();
465       scanResultCache.loadResultsToCache(values, callable.isHeartbeatMessage());
466       int numberOfCompleteRows =
467           scanResultCache.numberOfCompleteRows() - numberOfCompleteRowsBefore;
468       if (scanResultCache.getCount() > 0) {
469         remainingResultSize -= scanResultCache.getResultSize();
470         scanResultCache.resetResultSize();
471         countdown -= scanResultCache.getCount();
472         scanResultCache.resetCount();
473         this.lastResult = scanResultCache.getLastResult();
474       }
475 
476       if (scan.getLimit() > 0) {
477         int newLimit = scan.getLimit() - numberOfCompleteRows;
478         assert newLimit >= 0;
479         scan.setLimit(newLimit);
480       }
481       if (scanExhausted(values)) {
482         closeScanner();
483         closed = true;
484         break;
485       }
486       boolean regionExhausted = regionExhausted(values);
487       if (callable.isHeartbeatMessage()) {
488         if (!cache.isEmpty()) {
489           // Caller of this method just wants a Result. If we see a heartbeat message, it means
490           // processing of the scan is taking a long time server side. Rather than continue to
491           // loop until a limit (e.g. size or caching) is reached, break out early to avoid causing
492           // unnecesary delays to the caller
493           if (LOG.isTraceEnabled()) {
494             LOG.trace("Heartbeat message received and cache contains Results." +
495                 " Breaking out of scan loop");
496           }
497           // we know that the region has not been exhausted yet so just break without calling
498           // closeScannerIfExhausted
499           break;
500         }
501       }
502       if (cache.isEmpty() && !closed && scan.isNeedCursorResult()) {
503         if (callable.isHeartbeatMessage() && callable.getCursor() != null) {
504           // Use cursor row key from server
505           cache.add(Result.createCursorResult(callable.getCursor()));
506           break;
507         }
508         if (values.length > 0) {
509           // It is size limit exceed and we need return the last Result's row.
510           // When user setBatch and the scanner is reopened, the server may return Results that
511           // user has seen and the last Result can not be seen because the number is not enough.
512           // So the row keys of results may not be same, we must use the last one.
513           cache.add(Result.createCursorResult(new Cursor(values[values.length - 1].getRow())));
514           break;
515         }
516       }
517       if (countdown <= 0) {
518         // we have enough result.
519         closeScannerIfExhausted(regionExhausted);
520         break;
521       }
522       if (remainingResultSize <= 0) {
523         if (!cache.isEmpty()) {
524           closeScannerIfExhausted(regionExhausted);
525           break;
526         } else {
527           // we have reached the max result size but we still can not find anything to return to the
528           // user. Reset the maxResultSize and try again.
529           remainingResultSize = maxScannerResultSize;
530         }
531       }
532       // we are done with the current region
533       if (regionExhausted) {
534         if (!moveToNextRegion()) {
535           break;
536         }
537       }
538     }
539   }
540 
541   @Override
542   public void close() {
543     if (!scanMetricsPublished) writeScanMetrics();
544     if (callable != null) {
545       callable.setClose();
546       try {
547         call(callable, caller, scannerTimeout, false);
548       } catch (UnknownScannerException e) {
549         // We used to catch this error, interpret, and rethrow. However, we
550         // have since decided that it's not nice for a scanner's close to
551         // throw exceptions. Chances are it was just due to lease time out.
552       } catch (IOException e) {
553         /* An exception other than UnknownScanner is unexpected. */
554         LOG.warn("scanner failed to close. Exception follows: " + e);
555       }
556       callable = null;
557     }
558     closed = true;
559   }
560 
561   @Override
562   public boolean renewLease() {
563     if (callable != null) {
564       // do not return any rows, do not advance the scanner
565       callable.setRenew(true);
566       try {
567         this.caller.callWithoutRetries(callable, this.scannerTimeout);
568       } catch (Exception e) {
569         return false;
570       } finally {
571         callable.setRenew(false);
572       }
573       return true;
574     }
575     return false;
576   }
577 }