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