1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
51
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
60
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
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
78
79
80
81
82 protected final int primaryOperationTimeout;
83 private int retries;
84 protected final ExecutorService pool;
85
86
87
88
89
90
91
92
93
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
123 initScanMetrics(scan);
124
125
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
146
147
148
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
206
207
208
209
210 protected abstract boolean setNewStartKey();
211
212
213
214
215
216 protected abstract ScannerCallable createScannerCallable();
217
218
219
220
221
222
223
224 @VisibleForTesting
225 protected boolean moveToNextRegion() {
226
227 try {
228 closeScanner();
229 } catch (IOException e) {
230
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
246 LOG.debug(
247 "Advancing internal scanner to startKey at '" + Bytes.toStringBinary(scan.getStartRow()) +
248 "', " + (scan.includeStartRow() ? "inclusive" : "exclusive"));
249 }
250
251
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
274
275 Result[] rrs = caller.callWithoutRetries(callable, scannerTimeout);
276 if (currentRegion == null && updateCurrentRegion) {
277 currentRegion = callable.getHRegionInfo();
278 }
279 return rrs;
280 }
281
282
283
284
285
286
287
288
289
290 protected void writeScanMetrics() {
291 if (this.scanMetrics == null || scanMetricsPublished) {
292 return;
293 }
294
295
296
297
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
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
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
333
334
335
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
349
350 scanResultCache.clear();
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
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
372
373 if (retriesLeft <= 0) {
374 throw e;
375 }
376 } else {
377 throw e;
378 }
379
380
381 if (this.lastResult != null) {
382
383
384
385
386
387
388 scan.withStartRow(lastResult.getRow(), lastResult.mayHaveMoreCellsInRow());
389 }
390 if (e instanceof OutOfOrderScannerNextException) {
391 if (retryAfterOutOfOrderException.isTrue()) {
392 retryAfterOutOfOrderException.setValue(false);
393 } else {
394
395 throw new DoNotRetryIOException(
396 "Failed after retry of OutOfOrderScannerNextException: was there a rpc timeout?", e);
397 }
398 }
399
400 this.currentRegion = null;
401
402
403 callable = null;
404 }
405
406
407
408
409 protected void loadCache() throws IOException {
410
411 if (closed) {
412 return;
413 }
414 long remainingResultSize = maxScannerResultSize;
415 int countdown = this.caching;
416
417 if (callable == null) {
418 if (!moveToNextRegion()) {
419 return;
420 }
421 }
422
423
424 MutableBoolean retryAfterOutOfOrderException = new MutableBoolean(true);
425
426
427 int retriesLeft = getRetries();
428 for (;;) {
429 Result[] values;
430 try {
431
432
433
434
435
436 values = call(callable, caller, scannerTimeout, true);
437
438
439
440
441 if (callable.switchedToADifferentReplica()) {
442
443
444 scanResultCache.clear();
445 this.currentRegion = callable.getHRegionInfo();
446 }
447 retryAfterOutOfOrderException.setValue(true);
448 } catch (DoNotRetryIOException e) {
449 handleScanError(e, retryAfterOutOfOrderException, retriesLeft--);
450
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
462
463
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
490
491
492
493 if (LOG.isTraceEnabled()) {
494 LOG.trace("Heartbeat message received and cache contains Results." +
495 " Breaking out of scan loop");
496 }
497
498
499 break;
500 }
501 }
502 if (cache.isEmpty() && !closed && scan.isNeedCursorResult()) {
503 if (callable.isHeartbeatMessage() && callable.getCursor() != null) {
504
505 cache.add(Result.createCursorResult(callable.getCursor()));
506 break;
507 }
508 if (values.length > 0) {
509
510
511
512
513 cache.add(Result.createCursorResult(new Cursor(values[values.length - 1].getRow())));
514 break;
515 }
516 }
517 if (countdown <= 0) {
518
519 closeScannerIfExhausted(regionExhausted);
520 break;
521 }
522 if (remainingResultSize <= 0) {
523 if (!cache.isEmpty()) {
524 closeScannerIfExhausted(regionExhausted);
525 break;
526 } else {
527
528
529 remainingResultSize = maxScannerResultSize;
530 }
531 }
532
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
550
551
552 } catch (IOException e) {
553
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
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 }