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