View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase.client;
20  
21  import com.google.protobuf.ServiceException;
22  import com.google.protobuf.TextFormat;
23  
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  import org.apache.hadoop.classification.InterfaceAudience;
27  import org.apache.hadoop.classification.InterfaceStability;
28  import org.apache.hadoop.conf.Configuration;
29  import org.apache.hadoop.hbase.CellScanner;
30  import org.apache.hadoop.hbase.TableName;
31  import org.apache.hadoop.hbase.DoNotRetryIOException;
32  import org.apache.hadoop.hbase.HRegionInfo;
33  import org.apache.hadoop.hbase.HRegionLocation;
34  import org.apache.hadoop.hbase.KeyValue;
35  import org.apache.hadoop.hbase.NotServingRegionException;
36  import org.apache.hadoop.hbase.RemoteExceptionHandler;
37  import org.apache.hadoop.hbase.UnknownScannerException;
38  import org.apache.hadoop.hbase.client.metrics.ScanMetrics;
39  import org.apache.hadoop.hbase.ipc.PayloadCarryingRpcController;
40  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
41  import org.apache.hadoop.hbase.protobuf.RequestConverter;
42  import org.apache.hadoop.hbase.protobuf.ResponseConverter;
43  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ScanRequest;
44  import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ScanResponse;
45  import org.apache.hadoop.hbase.regionserver.RegionServerStoppedException;
46  import org.apache.hadoop.ipc.RemoteException;
47  import org.apache.hadoop.net.DNS;
48  
49  import java.io.IOException;
50  import java.net.UnknownHostException;
51  
52  /**
53   * Scanner operations such as create, next, etc.
54   * Used by {@link ResultScanner}s made by {@link HTable}. Passed to a retrying caller such as
55   * {@link RpcRetryingCaller} so fails are retried.
56   */
57  @InterfaceAudience.Public
58  @InterfaceStability.Stable
59  public class ScannerCallable extends RegionServerCallable<Result[]> {
60    public static final String LOG_SCANNER_LATENCY_CUTOFF
61      = "hbase.client.log.scanner.latency.cutoff";
62    public static final String LOG_SCANNER_ACTIVITY = "hbase.client.log.scanner.activity";
63  
64    public static final Log LOG = LogFactory.getLog(ScannerCallable.class);
65    private long scannerId = -1L;
66    private boolean instantiated = false;
67    private boolean closed = false;
68    private Scan scan;
69    private int caching = 1;
70    private ScanMetrics scanMetrics;
71    private boolean logScannerActivity = false;
72    private int logCutOffLatency = 1000;
73    private static String myAddress;
74    static {
75      try {
76        myAddress = DNS.getDefaultHost("default", "default");
77      } catch (UnknownHostException uhe) {
78        LOG.error("cannot determine my address", uhe);
79      }
80    }
81  
82    // indicate if it is a remote server call
83    private boolean isRegionServerRemote = true;
84    private long nextCallSeq = 0;
85    
86    /**
87     * @param connection which connection
88     * @param tableName table callable is on
89     * @param scan the scan to execute
90     * @param scanMetrics the ScanMetrics to used, if it is null, ScannerCallable
91     * won't collect metrics
92     */
93    public ScannerCallable (HConnection connection, TableName tableName, Scan scan,
94      ScanMetrics scanMetrics) {
95      super(connection, tableName, scan.getStartRow());
96      this.scan = scan;
97      this.scanMetrics = scanMetrics;
98      Configuration conf = connection.getConfiguration();
99      logScannerActivity = conf.getBoolean(LOG_SCANNER_ACTIVITY, false);
100     logCutOffLatency = conf.getInt(LOG_SCANNER_LATENCY_CUTOFF, 1000);
101   }
102 
103   /**
104    * @param reload force reload of server location
105    * @throws IOException
106    */
107   @Override
108   public void prepare(boolean reload) throws IOException {
109     if (!instantiated || reload) {
110       super.prepare(reload);
111       checkIfRegionServerIsRemote();
112       instantiated = true;
113     }
114 
115     // check how often we retry.
116     // HConnectionManager will call instantiateServer with reload==true
117     // if and only if for retries.
118     if (reload && this.scanMetrics != null) {
119       this.scanMetrics.countOfRPCRetries.incrementAndGet();
120       if (isRegionServerRemote) {
121         this.scanMetrics.countOfRemoteRPCRetries.incrementAndGet();
122       }
123     }
124   }
125 
126   /**
127    * compare the local machine hostname with region server's hostname
128    * to decide if hbase client connects to a remote region server
129    */
130   private void checkIfRegionServerIsRemote() {
131     if (getLocation().getHostname().equalsIgnoreCase(myAddress)) {
132       isRegionServerRemote = false;
133     } else {
134       isRegionServerRemote = true;
135     }
136   }
137 
138   /**
139    * @see java.util.concurrent.Callable#call()
140    */
141   public Result [] call() throws IOException {
142     if (closed) {
143       if (scannerId != -1) {
144         close();
145       }
146     } else {
147       if (scannerId == -1L) {
148         this.scannerId = openScanner();
149       } else {
150         Result [] rrs = null;
151         ScanRequest request = null;
152         try {
153           incRPCcallsMetrics();
154           request = RequestConverter.buildScanRequest(scannerId, caching, false, nextCallSeq);
155           ScanResponse response = null;
156           PayloadCarryingRpcController controller = new PayloadCarryingRpcController();
157           try {
158             response = getStub().scan(controller, request);
159             // Client and RS maintain a nextCallSeq number during the scan. Every next() call
160             // from client to server will increment this number in both sides. Client passes this
161             // number along with the request and at RS side both the incoming nextCallSeq and its
162             // nextCallSeq will be matched. In case of a timeout this increment at the client side
163             // should not happen. If at the server side fetching of next batch of data was over,
164             // there will be mismatch in the nextCallSeq number. Server will throw
165             // OutOfOrderScannerNextException and then client will reopen the scanner with startrow
166             // as the last successfully retrieved row.
167             // See HBASE-5974
168             nextCallSeq++;
169             long timestamp = System.currentTimeMillis();
170             // Results are returned via controller
171             CellScanner cellScanner = controller.cellScanner();
172             rrs = ResponseConverter.getResults(cellScanner, response);
173             if (logScannerActivity) {
174               long now = System.currentTimeMillis();
175               if (now - timestamp > logCutOffLatency) {
176                 int rows = rrs == null ? 0 : rrs.length;
177                 LOG.info("Took " + (now-timestamp) + "ms to fetch "
178                   + rows + " rows from scanner=" + scannerId);
179               }
180             }
181             if (response.hasMoreResults()
182                 && !response.getMoreResults()) {
183               scannerId = -1L;
184               closed = true;
185               return null;
186             }
187           } catch (ServiceException se) {
188             throw ProtobufUtil.getRemoteException(se);
189           }
190           updateResultsMetrics(rrs);
191         } catch (IOException e) {
192           if (logScannerActivity) {
193             LOG.info("Got exception making request " + TextFormat.shortDebugString(request), e);
194           }
195           IOException ioe = e;
196           if (e instanceof RemoteException) {
197             ioe = RemoteExceptionHandler.decodeRemoteException((RemoteException)e);
198           }
199           if (logScannerActivity && (ioe instanceof UnknownScannerException)) {
200             try {
201               HRegionLocation location =
202                 getConnection().relocateRegion(getTableName(), scan.getStartRow());
203               LOG.info("Scanner=" + scannerId
204                 + " expired, current region location is " + location.toString()
205                 + " ip:" + location.getHostnamePort());
206             } catch (Throwable t) {
207               LOG.info("Failed to relocate region", t);
208             }
209           }
210           // The below convertion of exceptions into DoNotRetryExceptions is a little strange.
211           // Why not just have these exceptions implment DNRIOE you ask?  Well, usually we want
212           // ServerCallable#withRetries to just retry when it gets these exceptions.  In here in
213           // a scan when doing a next in particular, we want to break out and get the scanner to
214           // reset itself up again.  Throwing a DNRIOE is how we signal this to happen (its ugly,
215           // yeah and hard to follow and in need of a refactor).
216           if (ioe instanceof NotServingRegionException) {
217             // Throw a DNRE so that we break out of cycle of calling NSRE
218             // when what we need is to open scanner against new location.
219             // Attach NSRE to signal client that it needs to re-setup scanner.
220             if (this.scanMetrics != null) {
221               this.scanMetrics.countOfNSRE.incrementAndGet();
222             }
223             throw new DoNotRetryIOException("Resetting the scanner -- see exception cause", ioe);
224           } else if (ioe instanceof RegionServerStoppedException) {
225             // Throw a DNRE so that we break out of cycle of the retries and instead go and
226             // open scanner against new location.
227             throw new DoNotRetryIOException("Resetting the scanner -- see exception cause", ioe);
228           } else {
229             // The outer layers will retry
230             throw ioe;
231           }
232         }
233         return rrs;
234       }
235     }
236     return null;
237   }
238 
239   private void incRPCcallsMetrics() {
240     if (this.scanMetrics == null) {
241       return;
242     }
243     this.scanMetrics.countOfRPCcalls.incrementAndGet();
244     if (isRegionServerRemote) {
245       this.scanMetrics.countOfRemoteRPCcalls.incrementAndGet();
246     }
247   }
248 
249   private void updateResultsMetrics(Result[] rrs) {
250     if (this.scanMetrics == null || rrs == null || rrs.length == 0) {
251       return;
252     }
253     long resultSize = 0;
254     for (Result rr : rrs) {
255       for (KeyValue kv : rr.raw()) {
256         resultSize += kv.getLength();
257       }
258     }
259     this.scanMetrics.countOfBytesInResults.addAndGet(resultSize);
260     if (isRegionServerRemote) {
261       this.scanMetrics.countOfBytesInRemoteResults.addAndGet(resultSize);
262     }
263   }
264 
265   private void close() {
266     if (this.scannerId == -1L) {
267       return;
268     }
269     try {
270       incRPCcallsMetrics();
271       ScanRequest request =
272         RequestConverter.buildScanRequest(this.scannerId, 0, true);
273       try {
274         getStub().scan(null, request);
275       } catch (ServiceException se) {
276         throw ProtobufUtil.getRemoteException(se);
277       }
278     } catch (IOException e) {
279       LOG.warn("Ignore, probably already closed", e);
280     }
281     this.scannerId = -1L;
282   }
283 
284   protected long openScanner() throws IOException {
285     incRPCcallsMetrics();
286     ScanRequest request =
287       RequestConverter.buildScanRequest(
288         getLocation().getRegionInfo().getRegionName(),
289         this.scan, 0, false);
290     try {
291       ScanResponse response = getStub().scan(null, request);
292       long id = response.getScannerId();
293       if (logScannerActivity) {
294         LOG.info("Open scanner=" + id + " for scan=" + scan.toString()
295           + " on region " + getLocation().toString() + " ip:"
296           + getLocation().getHostnamePort());
297       }
298       return id;
299     } catch (ServiceException se) {
300       throw ProtobufUtil.getRemoteException(se);
301     }
302   }
303 
304   protected Scan getScan() {
305     return scan;
306   }
307 
308   /**
309    * Call this when the next invocation of call should close the scanner
310    */
311   public void setClose() {
312     this.closed = true;
313   }
314 
315   /**
316    * @return the HRegionInfo for the current region
317    */
318   public HRegionInfo getHRegionInfo() {
319     if (!instantiated) {
320       return null;
321     }
322     return getLocation().getRegionInfo();
323   }
324 
325   /**
326    * Get the number of rows that will be fetched on next
327    * @return the number of rows for caching
328    */
329   public int getCaching() {
330     return caching;
331   }
332 
333   /**
334    * Set the number of rows that will be fetched on next
335    * @param caching the number of rows for caching
336    */
337   public void setCaching(int caching) {
338     this.caching = caching;
339   }
340 }