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 java.io.IOException;
21 import java.util.LinkedList;
22
23 import com.google.common.annotations.VisibleForTesting;
24 import org.apache.commons.logging.Log;
25 import org.apache.commons.logging.LogFactory;
26 import org.apache.hadoop.hbase.classification.InterfaceAudience;
27 import org.apache.hadoop.hbase.classification.InterfaceStability;
28 import org.apache.hadoop.conf.Configuration;
29 import org.apache.hadoop.hbase.Cell;
30 import org.apache.hadoop.hbase.DoNotRetryIOException;
31 import org.apache.hadoop.hbase.HBaseConfiguration;
32 import org.apache.hadoop.hbase.HConstants;
33 import org.apache.hadoop.hbase.HRegionInfo;
34 import org.apache.hadoop.hbase.KeyValueUtil;
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.exceptions.OutOfOrderScannerNextException;
39 import org.apache.hadoop.hbase.ipc.RpcControllerFactory;
40 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
41 import org.apache.hadoop.hbase.protobuf.generated.MapReduceProtos;
42 import org.apache.hadoop.hbase.regionserver.RegionServerStoppedException;
43 import org.apache.hadoop.hbase.util.Bytes;
44
45
46
47
48
49
50 @InterfaceAudience.Public
51 @InterfaceStability.Stable
52 public class ClientScanner extends AbstractClientScanner {
53 private final Log LOG = LogFactory.getLog(this.getClass());
54 protected Scan scan;
55 protected boolean closed = false;
56
57
58 protected HRegionInfo currentRegion = null;
59 protected ScannerCallable callable = null;
60 protected final LinkedList<Result> cache = new LinkedList<Result>();
61 protected final int caching;
62 protected long lastNext;
63
64 protected Result lastResult = null;
65 protected final long maxScannerResultSize;
66 private final HConnection connection;
67 private final TableName tableName;
68 protected final int scannerTimeout;
69 protected boolean scanMetricsPublished = false;
70 protected RpcRetryingCaller<Result []> caller;
71 protected RpcControllerFactory rpcControllerFactory;
72
73
74
75
76
77
78
79
80
81
82
83 @Deprecated
84 public ClientScanner(final Configuration conf, final Scan scan,
85 final TableName tableName) throws IOException {
86 this(conf, scan, tableName, HConnectionManager.getConnection(conf));
87 }
88
89
90
91
92 @Deprecated
93 public ClientScanner(final Configuration conf, final Scan scan,
94 final byte [] tableName) throws IOException {
95 this(conf, scan, TableName.valueOf(tableName));
96 }
97
98
99
100
101
102
103
104
105
106
107
108
109 public ClientScanner(final Configuration conf, final Scan scan, final TableName tableName,
110 HConnection connection) throws IOException {
111 this(conf, scan, tableName, connection,
112 RpcRetryingCallerFactory.instantiate(conf, connection.getStatisticsTracker()),
113 RpcControllerFactory.instantiate(conf));
114 }
115
116
117
118
119 @Deprecated
120 public ClientScanner(final Configuration conf, final Scan scan, final byte [] tableName,
121 HConnection connection) throws IOException {
122 this(conf, scan, TableName.valueOf(tableName), connection, new RpcRetryingCallerFactory(conf),
123 RpcControllerFactory.instantiate(conf));
124 }
125
126
127
128
129
130
131
132 @Deprecated
133 public ClientScanner(final Configuration conf, final Scan scan, final TableName tableName,
134 HConnection connection, RpcRetryingCallerFactory rpcFactory) throws IOException {
135 this(conf, scan, tableName, connection, rpcFactory, RpcControllerFactory.instantiate(conf));
136 }
137
138
139
140
141
142
143
144
145
146
147 public ClientScanner(final Configuration conf, final Scan scan, final TableName tableName,
148 HConnection connection, RpcRetryingCallerFactory rpcFactory,
149 RpcControllerFactory controllerFactory) throws IOException {
150 if (LOG.isTraceEnabled()) {
151 LOG.trace("Scan table=" + tableName
152 + ", startRow=" + Bytes.toStringBinary(scan.getStartRow()));
153 }
154 this.scan = scan;
155 this.tableName = tableName;
156 this.lastNext = System.currentTimeMillis();
157 this.connection = connection;
158 if (scan.getMaxResultSize() > 0) {
159 this.maxScannerResultSize = scan.getMaxResultSize();
160 } else {
161 this.maxScannerResultSize = conf.getLong(
162 HConstants.HBASE_CLIENT_SCANNER_MAX_RESULT_SIZE_KEY,
163 HConstants.DEFAULT_HBASE_CLIENT_SCANNER_MAX_RESULT_SIZE);
164 }
165 this.scannerTimeout = HBaseConfiguration.getInt(conf,
166 HConstants.HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD,
167 HConstants.HBASE_REGIONSERVER_LEASE_PERIOD_KEY,
168 HConstants.DEFAULT_HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD);
169
170
171 initScanMetrics(scan);
172
173
174 if (this.scan.getCaching() > 0) {
175 this.caching = this.scan.getCaching();
176 } else {
177 this.caching = conf.getInt(
178 HConstants.HBASE_CLIENT_SCANNER_CACHING,
179 HConstants.DEFAULT_HBASE_CLIENT_SCANNER_CACHING);
180 }
181
182 this.caller = rpcFactory.<Result[]> newCaller();
183 this.rpcControllerFactory = controllerFactory;
184
185 initializeScannerInConstruction();
186 }
187
188 protected void initializeScannerInConstruction() throws IOException{
189
190 nextScanner(this.caching, false);
191 }
192
193 protected HConnection getConnection() {
194 return this.connection;
195 }
196
197
198
199
200
201 @Deprecated
202 protected byte [] getTableName() {
203 return this.tableName.getName();
204 }
205
206 protected TableName getTable() {
207 return this.tableName;
208 }
209
210 protected Scan getScan() {
211 return scan;
212 }
213
214 protected long getTimestamp() {
215 return lastNext;
216 }
217
218 protected long getMaxResultSize() {
219 return maxScannerResultSize;
220 }
221
222
223 protected boolean checkScanStopRow(final byte [] endKey) {
224 if (this.scan.getStopRow().length > 0) {
225
226 byte [] stopRow = scan.getStopRow();
227 int cmp = Bytes.compareTo(stopRow, 0, stopRow.length,
228 endKey, 0, endKey.length);
229 if (cmp <= 0) {
230
231
232 return true;
233 }
234 }
235 return false;
236 }
237
238
239
240
241
242
243
244
245
246
247 protected boolean nextScanner(int nbRows, final boolean done)
248 throws IOException {
249
250 if (this.callable != null) {
251 this.callable.setClose();
252 this.caller.callWithRetries(callable);
253 this.callable = null;
254 }
255
256
257 byte [] localStartKey;
258
259
260 if (this.currentRegion != null) {
261 byte [] endKey = this.currentRegion.getEndKey();
262 if (endKey == null ||
263 Bytes.equals(endKey, HConstants.EMPTY_BYTE_ARRAY) ||
264 checkScanStopRow(endKey) ||
265 done) {
266 close();
267 if (LOG.isTraceEnabled()) {
268 LOG.trace("Finished " + this.currentRegion);
269 }
270 return false;
271 }
272 localStartKey = endKey;
273 if (LOG.isTraceEnabled()) {
274 LOG.trace("Finished " + this.currentRegion);
275 }
276 } else {
277 localStartKey = this.scan.getStartRow();
278 }
279
280 if (LOG.isDebugEnabled() && this.currentRegion != null) {
281
282 LOG.debug("Advancing internal scanner to startKey at '" +
283 Bytes.toStringBinary(localStartKey) + "'");
284 }
285 try {
286 callable = getScannerCallable(localStartKey, nbRows);
287
288
289 this.caller.callWithRetries(callable);
290 this.currentRegion = callable.getHRegionInfo();
291 if (this.scanMetrics != null) {
292 this.scanMetrics.countOfRegions.incrementAndGet();
293 }
294 } catch (IOException e) {
295 close();
296 throw e;
297 }
298 return true;
299 }
300
301 @InterfaceAudience.Private
302 protected ScannerCallable getScannerCallable(byte [] localStartKey,
303 int nbRows) {
304 scan.setStartRow(localStartKey);
305 ScannerCallable s = new ScannerCallable(getConnection(),
306 getTable(), scan, this.scanMetrics, rpcControllerFactory.newController());
307 s.setCaching(nbRows);
308 return s;
309 }
310
311
312
313
314
315
316
317
318
319
320
321
322 protected void writeScanMetrics() {
323 if (this.scanMetrics == null || scanMetricsPublished) {
324 return;
325 }
326 MapReduceProtos.ScanMetrics pScanMetrics = ProtobufUtil.toScanMetrics(scanMetrics);
327 scan.setAttribute(Scan.SCAN_ATTRIBUTES_METRICS_DATA, pScanMetrics.toByteArray());
328 scanMetricsPublished = true;
329 }
330
331 @Override
332 public Result next() throws IOException {
333
334 if (cache.size() == 0 && this.closed) {
335 return null;
336 }
337 if (cache.size() == 0) {
338 loadCache();
339 }
340
341 if (cache.size() > 0) {
342 return cache.poll();
343 }
344
345
346 writeScanMetrics();
347 return null;
348 }
349
350 @VisibleForTesting
351 public int getCacheSize() {
352 return cache != null ? cache.size() : 0;
353 }
354
355
356
357
358 protected void loadCache() throws IOException {
359 Result [] values = null;
360 long remainingResultSize = maxScannerResultSize;
361 int countdown = this.caching;
362
363
364 callable.setCaching(this.caching);
365
366
367 boolean skipFirst = false;
368 boolean retryAfterOutOfOrderException = true;
369
370
371 boolean serverHasMoreResults = false;
372 do {
373 try {
374 if (skipFirst) {
375
376
377 callable.setCaching(1);
378 values = this.caller.callWithRetries(callable);
379 callable.setCaching(this.caching);
380 skipFirst = false;
381 }
382
383
384
385 values = this.caller.callWithRetries(callable);
386 if (skipFirst && values != null && values.length == 1) {
387 skipFirst = false;
388 values = this.caller.callWithRetries(callable);
389 }
390 retryAfterOutOfOrderException = true;
391 } catch (DoNotRetryIOException e) {
392
393
394 if (e instanceof UnknownScannerException) {
395 long timeout = lastNext + scannerTimeout;
396
397
398
399 if (timeout < System.currentTimeMillis()) {
400 LOG.info("For hints related to the following exception, please try taking a look at: " +
401 "https://hbase.apache.org/book.html#trouble.client.scantimeout");
402 long elapsed = System.currentTimeMillis() - lastNext;
403 ScannerTimeoutException ex = new ScannerTimeoutException(
404 elapsed + "ms passed since the last invocation, " +
405 "timeout is currently set to " + scannerTimeout);
406 ex.initCause(e);
407 throw ex;
408 }
409 } else {
410
411
412 Throwable cause = e.getCause();
413 if ((cause != null && cause instanceof NotServingRegionException) ||
414 (cause != null && cause instanceof RegionServerStoppedException) ||
415 e instanceof OutOfOrderScannerNextException) {
416
417
418
419 } else {
420 throw e;
421 }
422 }
423
424 if (this.lastResult != null) {
425
426
427
428
429
430
431 this.scan.setStartRow(this.lastResult.getRow());
432
433
434
435 skipFirst = true;
436 }
437 if (e instanceof OutOfOrderScannerNextException) {
438 if (retryAfterOutOfOrderException) {
439 retryAfterOutOfOrderException = false;
440 } else {
441
442 throw new DoNotRetryIOException("Failed after retry of " +
443 "OutOfOrderScannerNextException: was there a rpc timeout?", e);
444 }
445 }
446
447 this.currentRegion = null;
448
449
450 callable = null;
451
452 continue;
453 }
454 long currentTime = System.currentTimeMillis();
455 if (this.scanMetrics != null ) {
456 this.scanMetrics.sumOfMillisSecBetweenNexts.addAndGet(currentTime-lastNext);
457 }
458 lastNext = currentTime;
459 if (values != null && values.length > 0) {
460 for (Result rs : values) {
461 cache.add(rs);
462 for (Cell kv : rs.rawCells()) {
463
464 remainingResultSize -= KeyValueUtil.ensureKeyValue(kv).heapSize();
465 }
466 countdown--;
467 this.lastResult = rs;
468 }
469 }
470
471
472
473
474 if (null != values && values.length > 0 && callable.hasMoreResultsContext()) {
475
476
477 serverHasMoreResults = callable.getServerHasMoreResults();
478 }
479
480
481
482
483 } while (remainingResultSize > 0 && countdown > 0 && !serverHasMoreResults
484 && nextScanner(countdown, values == null));
485 }
486
487 @Override
488 public void close() {
489 if (!scanMetricsPublished) writeScanMetrics();
490 if (callable != null) {
491 callable.setClose();
492 try {
493 this.caller.callWithRetries(callable);
494 } catch (UnknownScannerException e) {
495
496
497
498 } catch (IOException e) {
499
500 LOG.warn("scanner failed to close. Exception follows: " + e);
501 }
502 callable = null;
503 }
504 closed = true;
505 }
506
507 @Override
508 public boolean renewLease() {
509 if (callable != null) {
510
511 callable.setRenew(true);
512 try {
513 this.caller.callWithoutRetries(callable, this.scannerTimeout);
514 } catch (Exception e) {
515 return false;
516 } finally {
517 callable.setRenew(false);
518 }
519 return true;
520 }
521 return false;
522 }
523 }