View Javadoc

1   /**
2    * Copyright The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one or more
5    * contributor license agreements. See the NOTICE file distributed with this
6    * work for additional information regarding copyright ownership. The ASF
7    * licenses this file to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   * http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16   * License for the specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.hadoop.hbase.client;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.LinkedList;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.apache.hadoop.classification.InterfaceAudience;
28  import org.apache.hadoop.classification.InterfaceStability;
29  import org.apache.hadoop.conf.Configuration;
30  import org.apache.hadoop.hbase.Cell;
31  import org.apache.hadoop.hbase.HConstants;
32  import org.apache.hadoop.hbase.HRegionInfo;
33  import org.apache.hadoop.hbase.KeyValueUtil;
34  import org.apache.hadoop.hbase.TableName;
35  import org.apache.hadoop.hbase.ipc.PayloadCarryingRpcController;
36  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
37  import org.apache.hadoop.hbase.protobuf.RequestConverter;
38  import org.apache.hadoop.hbase.protobuf.ResponseConverter;
39  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ScanRequest;
40  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ScanResponse;
41  import org.apache.hadoop.hbase.util.Bytes;
42  
43  import com.google.protobuf.ServiceException;
44  
45  /**
46   * Client scanner for small scan. Generally, only one RPC is called to fetch the
47   * scan results, unless the results cross multiple regions or the row count of
48   * results excess the caching.
49   * 
50   * For small scan, it will get better performance than {@link ClientScanner}
51   */
52  @InterfaceAudience.Public
53  @InterfaceStability.Evolving
54  public class ClientSmallScanner extends ClientScanner {
55    private final Log LOG = LogFactory.getLog(this.getClass());
56    private RegionServerCallable<Result[]> smallScanCallable = null;
57    // When fetching results from server, skip the first result if it has the same
58    // row with this one
59    private byte[] skipRowOfFirstResult = null;
60  
61    /**
62     * Create a new ClientSmallScanner for the specified table. An HConnection
63     * will be retrieved using the passed Configuration. Note that the passed
64     * {@link Scan} 's start row maybe changed.
65     * 
66     * @param conf The {@link Configuration} to use.
67     * @param scan {@link Scan} to use in this scanner
68     * @param tableName The table that we wish to rangeGet
69     * @throws IOException
70     */
71    public ClientSmallScanner(final Configuration conf, final Scan scan,
72        final TableName tableName) throws IOException {
73      this(conf, scan, tableName, HConnectionManager.getConnection(conf));
74    }
75  
76    /**
77     * Create a new ClientSmallScanner for the specified table. An HConnection
78     * will be retrieved using the passed Configuration. Note that the passed
79     * {@link Scan} 's start row maybe changed.
80     * @param conf
81     * @param scan
82     * @param tableName
83     * @param connection
84     * @throws IOException
85     */
86    public ClientSmallScanner(final Configuration conf, final Scan scan,
87        final TableName tableName, HConnection connection) throws IOException {
88      this(conf, scan, tableName, connection, new RpcRetryingCallerFactory(conf));
89    }
90  
91    /**
92     * Create a new ShortClientScanner for the specified table Note that the
93     * passed {@link Scan}'s start row maybe changed changed.
94     * 
95     * @param conf The {@link Configuration} to use.
96     * @param scan {@link Scan} to use in this scanner
97     * @param tableName The table that we wish to rangeGet
98     * @param connection Connection identifying the cluster
99     * @param rpcFactory
100    * @throws IOException
101    */
102   public ClientSmallScanner(final Configuration conf, final Scan scan,
103       final TableName tableName, HConnection connection,
104       RpcRetryingCallerFactory rpcFactory) throws IOException {
105     super(conf, scan, tableName, connection, rpcFactory);
106   }
107 
108   @Override
109   protected void initializeScannerInConstruction() throws IOException {
110     // No need to initialize the scanner when constructing instance, do it when
111     // calling next(). Do nothing here.
112   }
113 
114   /**
115    * Gets a scanner for following scan. Move to next region or continue from the
116    * last result or start from the start row.
117    * @param nbRows
118    * @param done true if Server-side says we're done scanning.
119    * @param currentRegionDone true if scan is over on current region
120    * @return true if has next scanner
121    * @throws IOException
122    */
123   private boolean nextScanner(int nbRows, final boolean done,
124       boolean currentRegionDone) throws IOException {
125     // Where to start the next getter
126     byte[] localStartKey;
127     int cacheNum = nbRows;
128     skipRowOfFirstResult = null;
129     // if we're at end of table, close and return false to stop iterating
130     if (this.currentRegion != null && currentRegionDone) {
131       byte[] endKey = this.currentRegion.getEndKey();
132       if (endKey == null || Bytes.equals(endKey, HConstants.EMPTY_BYTE_ARRAY)
133           || checkScanStopRow(endKey) || done) {
134         close();
135         if (LOG.isDebugEnabled()) {
136           LOG.debug("Finished with small scan at " + this.currentRegion);
137         }
138         return false;
139       }
140       localStartKey = endKey;
141       if (LOG.isDebugEnabled()) {
142         LOG.debug("Finished with region " + this.currentRegion);
143       }
144     } else if (this.lastResult != null) {
145       localStartKey = this.lastResult.getRow();
146       skipRowOfFirstResult = this.lastResult.getRow();
147       cacheNum++;
148     } else {
149       localStartKey = this.scan.getStartRow();
150     }
151 
152     if (LOG.isTraceEnabled()) {
153       LOG.trace("Advancing internal small scanner to startKey at '"
154           + Bytes.toStringBinary(localStartKey) + "'");
155     }
156     smallScanCallable = getSmallScanCallable(localStartKey, cacheNum);
157     if (this.scanMetrics != null && skipRowOfFirstResult == null) {
158       this.scanMetrics.countOfRegions.incrementAndGet();
159     }
160     return true;
161   }
162 
163   private RegionServerCallable<Result[]> getSmallScanCallable(
164       byte[] localStartKey, final int cacheNum) {
165     this.scan.setStartRow(localStartKey);
166     RegionServerCallable<Result[]> callable = new RegionServerCallable<Result[]>(
167         getConnection(), getTable(), scan.getStartRow()) {
168       public Result[] call() throws IOException {
169         ScanRequest request = RequestConverter.buildScanRequest(getLocation()
170             .getRegionInfo().getRegionName(), scan, cacheNum, true);
171         ScanResponse response = null;
172         PayloadCarryingRpcController controller = new PayloadCarryingRpcController();
173         try {
174           controller.setPriority(getTableName());
175           response = getStub().scan(controller, request);
176           return ResponseConverter.getResults(controller.cellScanner(),
177               response);
178         } catch (ServiceException se) {
179           throw ProtobufUtil.getRemoteException(se);
180         }
181       }
182     };
183     return callable;
184   }
185 
186   @Override
187   public Result next() throws IOException {
188     // If the scanner is closed and there's nothing left in the cache, next is a
189     // no-op.
190     if (cache.size() == 0 && this.closed) {
191       return null;
192     }
193     if (cache.size() == 0) {
194       Result[] values = null;
195       long remainingResultSize = maxScannerResultSize;
196       int countdown = this.caching;
197       boolean currentRegionDone = false;
198       // Values == null means server-side filter has determined we must STOP
199       while (remainingResultSize > 0 && countdown > 0
200           && nextScanner(countdown, values == null, currentRegionDone)) {
201         // Server returns a null values if scanning is to stop. Else,
202         // returns an empty array if scanning is to go on and we've just
203         // exhausted current region.
204         values = this.caller.callWithRetries(smallScanCallable);
205         this.currentRegion = smallScanCallable.getHRegionInfo();
206         long currentTime = System.currentTimeMillis();
207         if (this.scanMetrics != null) {
208           this.scanMetrics.sumOfMillisSecBetweenNexts.addAndGet(currentTime
209               - lastNext);
210         }
211         lastNext = currentTime;
212         if (values != null && values.length > 0) {
213           for (int i = 0; i < values.length; i++) {
214             Result rs = values[i];
215             if (i == 0 && this.skipRowOfFirstResult != null
216                 && Bytes.equals(skipRowOfFirstResult, rs.getRow())) {
217               // Skip the first result
218               continue;
219             }
220             cache.add(rs);
221             for (Cell kv : rs.rawCells()) {
222               remainingResultSize -= KeyValueUtil.ensureKeyValue(kv).heapSize();
223             }
224             countdown--;
225             this.lastResult = rs;
226           }
227         }
228         currentRegionDone = countdown > 0;
229       }
230     }
231 
232     if (cache.size() > 0) {
233       return cache.poll();
234     }
235     // if we exhausted this scanner before calling close, write out the scan
236     // metrics
237     writeScanMetrics();
238     return null;
239   }
240 
241   @Override
242   public void close() {
243     if (!scanMetricsPublished) writeScanMetrics();
244     closed = true;
245   }
246 }