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 java.io.IOException;
23 import java.io.InterruptedIOException;
24 import java.util.LinkedList;
25 import java.util.concurrent.ExecutorService;
26
27 import org.apache.commons.lang.mutable.MutableBoolean;
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30 import org.apache.hadoop.conf.Configuration;
31 import org.apache.hadoop.hbase.DoNotRetryIOException;
32 import org.apache.hadoop.hbase.HConstants;
33 import org.apache.hadoop.hbase.HRegionInfo;
34 import org.apache.hadoop.hbase.regionserver.LeaseException;
35 import org.apache.hadoop.hbase.NotServingRegionException;
36 import org.apache.hadoop.hbase.TableName;
37 import org.apache.hadoop.hbase.UnknownScannerException;
38 import org.apache.hadoop.hbase.classification.InterfaceAudience;
39 import org.apache.hadoop.hbase.client.ScannerCallable.MoreResults;
40 import org.apache.hadoop.hbase.exceptions.OutOfOrderScannerNextException;
41 import org.apache.hadoop.hbase.exceptions.ScannerResetException;
42 import org.apache.hadoop.hbase.ipc.RpcControllerFactory;
43 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
44 import org.apache.hadoop.hbase.regionserver.RegionServerStoppedException;
45 import org.apache.hadoop.hbase.util.Bytes;
46
47
48
49
50
51 @InterfaceAudience.Private
52 public abstract class ClientScanner extends AbstractClientScanner {
53 private static final Log LOG = LogFactory.getLog(ClientScanner.class);
54
55 protected final Scan scan;
56 protected boolean closed = false;
57
58
59 protected HRegionInfo currentRegion = null;
60 protected ScannerCallableWithReplicas callable = null;
61 protected final LinkedList<Result> cache = new LinkedList<Result>();
62 private final ScanResultCache scanResultCache;
63 protected final int caching;
64 protected long lastNext;
65
66 protected Result lastResult = null;
67 protected final long maxScannerResultSize;
68 private final ClusterConnection connection;
69 private final TableName tableName;
70 protected final int scannerTimeout;
71 protected boolean scanMetricsPublished = false;
72 protected RpcRetryingCaller<Result[]> caller;
73 protected RpcControllerFactory rpcControllerFactory;
74 protected Configuration conf;
75
76
77
78
79
80 protected final int primaryOperationTimeout;
81 private int retries;
82 protected final ExecutorService pool;
83
84
85
86
87
88
89
90
91
92
93 public ClientScanner(final Configuration conf, final Scan scan, final TableName tableName,
94 ClusterConnection connection, RpcRetryingCallerFactory rpcFactory,
95 RpcControllerFactory controllerFactory, ExecutorService pool, int primaryOperationTimeout)
96 throws IOException {
97 if (LOG.isTraceEnabled()) {
98 LOG.trace(
99 "Scan table=" + tableName + ", startRow=" + Bytes.toStringBinary(scan.getStartRow()));
100 }
101 this.scan = scan;
102 this.tableName = tableName;
103 this.lastNext = System.currentTimeMillis();
104 this.connection = connection;
105 this.pool = pool;
106 this.primaryOperationTimeout = primaryOperationTimeout;
107 this.retries = conf.getInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER,
108 HConstants.DEFAULT_HBASE_CLIENT_RETRIES_NUMBER);
109 if (scan.getMaxResultSize() > 0) {
110 this.maxScannerResultSize = scan.getMaxResultSize();
111 } else {
112 this.maxScannerResultSize = conf.getLong(HConstants.HBASE_CLIENT_SCANNER_MAX_RESULT_SIZE_KEY,
113 HConstants.DEFAULT_HBASE_CLIENT_SCANNER_MAX_RESULT_SIZE);
114 }
115 this.scannerTimeout = conf.getInt(HConstants.HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD,
116 HConstants.DEFAULT_HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD);
117
118
119 initScanMetrics(scan);
120
121
122 if (this.scan.getCaching() > 0) {
123 this.caching = this.scan.getCaching();
124 } else {
125 this.caching = conf.getInt(HConstants.HBASE_CLIENT_SCANNER_CACHING,
126 HConstants.DEFAULT_HBASE_CLIENT_SCANNER_CACHING);
127 }
128
129 this.caller = rpcFactory.<Result[]> newCaller();
130 this.rpcControllerFactory = controllerFactory;
131
132 this.conf = conf;
133 this.scanResultCache = createScanResultCache(scan, cache);
134 }
135
136 protected ClusterConnection getConnection() {
137 return this.connection;
138 }
139
140
141
142
143
144
145
146 @Deprecated
147 protected byte[] getTableName() {
148 return this.tableName.getName();
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 protected long getMaxResultSize() {
188 return maxScannerResultSize;
189 }
190
191 private void closeScanner() throws IOException {
192 if (this.callable != null) {
193 this.callable.setClose();
194 call(callable, caller, scannerTimeout, false);
195 this.callable = null;
196 }
197 }
198
199
200
201
202
203
204
205 protected abstract boolean setNewStartKey();
206
207
208
209
210
211 protected abstract ScannerCallable createScannerCallable();
212
213
214
215
216
217
218
219 protected boolean moveToNextRegion() {
220
221 try {
222 closeScanner();
223 } catch (IOException e) {
224
225 if (LOG.isDebugEnabled()) {
226 LOG.debug("close scanner for " + currentRegion + " failed", e);
227 }
228 }
229 if (currentRegion != null) {
230 if (!setNewStartKey()) {
231 return false;
232 }
233 scan.resetMvccReadPoint();
234 if (LOG.isTraceEnabled()) {
235 LOG.trace("Finished " + this.currentRegion);
236 }
237 }
238 if (LOG.isDebugEnabled() && this.currentRegion != null) {
239
240 LOG.debug(
241 "Advancing internal scanner to startKey at '" + Bytes.toStringBinary(scan.getStartRow()) +
242 "', " + (scan.includeStartRow() ? "inclusive" : "exclusive"));
243 }
244
245
246 this.currentRegion = null;
247 this.callable =
248 new ScannerCallableWithReplicas(getTable(), getConnection(), createScannerCallable(), pool,
249 primaryOperationTimeout, scan, getRetries(), scannerTimeout, caching, conf, caller);
250 this.callable.setCaching(this.caching);
251 if (this.scanMetrics != null) {
252 this.scanMetrics.countOfRegions.incrementAndGet();
253 }
254 return true;
255 }
256
257 boolean isAnyRPCcancelled() {
258 return callable.isAnyRPCcancelled();
259 }
260
261 private Result[] call(ScannerCallableWithReplicas callable, RpcRetryingCaller<Result[]> caller,
262 int scannerTimeout, boolean updateCurrentRegion) throws IOException {
263 if (Thread.interrupted()) {
264 throw new InterruptedIOException();
265 }
266
267
268 Result[] rrs = caller.callWithoutRetries(callable, scannerTimeout);
269 if (currentRegion == null && updateCurrentRegion) {
270 currentRegion = callable.getHRegionInfo();
271 }
272 return rrs;
273 }
274
275
276
277
278
279
280
281
282
283 protected void writeScanMetrics() {
284 if (this.scanMetrics == null || scanMetricsPublished) {
285 return;
286 }
287
288
289
290
291 scan.setAttribute(Scan.SCAN_ATTRIBUTES_METRICS_DATA,
292 ProtobufUtil.toScanMetrics(scanMetrics, false).toByteArray());
293 scanMetricsPublished = true;
294 }
295
296 @Override
297 public Result next() throws IOException {
298
299 if (cache.size() == 0 && this.closed) {
300 return null;
301 }
302 if (cache.size() == 0) {
303 loadCache();
304 }
305
306 if (cache.size() > 0) {
307 return cache.poll();
308 }
309
310
311 writeScanMetrics();
312 return null;
313 }
314
315 public int getCacheSize() {
316 return cache != null ? cache.size() : 0;
317 }
318
319 private boolean scanExhausted(Result[] values) {
320 return callable.moreResultsForScan() == MoreResults.NO;
321 }
322
323 private boolean regionExhausted(Result[] values) {
324
325
326
327
328 return (values.length == 0 && !callable.isHeartbeatMessage()) ||
329 callable.moreResultsInRegion() == MoreResults.NO;
330 }
331
332 private void closeScannerIfExhausted(boolean exhausted) throws IOException {
333 if (exhausted) {
334 closeScanner();
335 }
336 }
337
338 private void handleScanError(DoNotRetryIOException e,
339 MutableBoolean retryAfterOutOfOrderException, int retriesLeft) throws DoNotRetryIOException {
340
341
342 scanResultCache.clear();
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358 Throwable cause = e.getCause();
359 if ((cause != null && cause instanceof NotServingRegionException) ||
360 (cause != null && cause instanceof RegionServerStoppedException) ||
361 e instanceof OutOfOrderScannerNextException || e instanceof UnknownScannerException ||
362 e instanceof ScannerResetException || e instanceof LeaseException) {
363
364
365 if (retriesLeft <= 0) {
366 throw e;
367 }
368 } else {
369 throw e;
370 }
371
372
373 if (this.lastResult != null) {
374
375
376
377
378
379
380 scan.withStartRow(lastResult.getRow(), lastResult.mayHaveMoreCellsInRow());
381 }
382 if (e instanceof OutOfOrderScannerNextException) {
383 if (retryAfterOutOfOrderException.isTrue()) {
384 retryAfterOutOfOrderException.setValue(false);
385 } else {
386
387 throw new DoNotRetryIOException(
388 "Failed after retry of OutOfOrderScannerNextException: was there a rpc timeout?", e);
389 }
390 }
391
392 this.currentRegion = null;
393
394
395 callable = null;
396 }
397
398
399
400
401 protected void loadCache() throws IOException {
402
403 if (closed) {
404 return;
405 }
406 long remainingResultSize = maxScannerResultSize;
407 int countdown = this.caching;
408
409 if (callable == null) {
410 if (!moveToNextRegion()) {
411 return;
412 }
413 }
414
415
416 MutableBoolean retryAfterOutOfOrderException = new MutableBoolean(true);
417
418
419 int retriesLeft = getRetries();
420 for (;;) {
421 Result[] values;
422 try {
423
424
425
426
427
428 values = call(callable, caller, scannerTimeout, true);
429
430
431
432
433 if (callable.switchedToADifferentReplica()) {
434
435
436 scanResultCache.clear();
437 this.currentRegion = callable.getHRegionInfo();
438 }
439 retryAfterOutOfOrderException.setValue(true);
440 } catch (DoNotRetryIOException e) {
441 handleScanError(e, retryAfterOutOfOrderException, retriesLeft--);
442
443 if (!moveToNextRegion()) {
444 break;
445 }
446 continue;
447 }
448 long currentTime = System.currentTimeMillis();
449 if (this.scanMetrics != null) {
450 this.scanMetrics.sumOfMillisSecBetweenNexts.addAndGet(currentTime - lastNext);
451 }
452 lastNext = currentTime;
453
454
455
456 int numberOfCompleteRowsBefore = scanResultCache.numberOfCompleteRows();
457 scanResultCache.loadResultsToCache(values, callable.isHeartbeatMessage());
458 int numberOfCompleteRows =
459 scanResultCache.numberOfCompleteRows() - numberOfCompleteRowsBefore;
460 if (scanResultCache.getCount() > 0) {
461 remainingResultSize -= scanResultCache.getResultSize();
462 scanResultCache.resetResultSize();
463 countdown -= scanResultCache.getCount();
464 scanResultCache.resetCount();
465 this.lastResult = scanResultCache.getLastResult();
466 }
467
468 if (scan.getLimit() > 0) {
469 int newLimit = scan.getLimit() - numberOfCompleteRows;
470 assert newLimit >= 0;
471 scan.setLimit(newLimit);
472 }
473 if (scanExhausted(values)) {
474 closeScanner();
475 closed = true;
476 break;
477 }
478 boolean regionExhausted = regionExhausted(values);
479 if (callable.isHeartbeatMessage()) {
480 if (!cache.isEmpty()) {
481
482
483
484
485 if (LOG.isTraceEnabled()) {
486 LOG.trace("Heartbeat message received and cache contains Results." +
487 " Breaking out of scan loop");
488 }
489
490
491 break;
492 }
493 }
494 if (cache.isEmpty() && !closed && scan.isNeedCursorResult()) {
495 if (callable.isHeartbeatMessage() && callable.getCursor() != null) {
496
497 cache.add(Result.createCursorResult(callable.getCursor()));
498 break;
499 }
500 if (values.length > 0) {
501
502
503
504
505 cache.add(Result.createCursorResult(new Cursor(values[values.length - 1].getRow())));
506 break;
507 }
508 }
509 if (countdown <= 0) {
510
511 closeScannerIfExhausted(regionExhausted);
512 break;
513 }
514 if (remainingResultSize <= 0) {
515 if (!cache.isEmpty()) {
516 closeScannerIfExhausted(regionExhausted);
517 break;
518 } else {
519
520
521 remainingResultSize = maxScannerResultSize;
522 }
523 }
524
525 if (regionExhausted) {
526 if (!moveToNextRegion()) {
527 break;
528 }
529 }
530 }
531 }
532
533 @Override
534 public void close() {
535 if (!scanMetricsPublished) writeScanMetrics();
536 if (callable != null) {
537 callable.setClose();
538 try {
539 call(callable, caller, scannerTimeout, false);
540 } catch (UnknownScannerException e) {
541
542
543
544 } catch (IOException e) {
545
546 LOG.warn("scanner failed to close. Exception follows: " + e);
547 }
548 callable = null;
549 }
550 closed = true;
551 }
552
553 @Override
554 public boolean renewLease() {
555 if (callable != null) {
556
557 callable.setRenew(true);
558 try {
559 this.caller.callWithoutRetries(callable, this.scannerTimeout);
560 } catch (Exception e) {
561 return false;
562 } finally {
563 callable.setRenew(false);
564 }
565 return true;
566 }
567 return false;
568 }
569 }