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 java.io.IOException;
22  import java.net.UnknownHostException;
23  
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  import org.apache.hadoop.conf.Configuration;
27  import org.apache.hadoop.hbase.client.metrics.ScanMetrics;
28  import org.apache.hadoop.hbase.DoNotRetryIOException;
29  import org.apache.hadoop.hbase.HRegionInfo;
30  import org.apache.hadoop.hbase.HRegionLocation;
31  import org.apache.hadoop.hbase.NotServingRegionException;
32  import org.apache.hadoop.hbase.RemoteExceptionHandler;
33  import org.apache.hadoop.hbase.UnknownScannerException;
34  import org.apache.hadoop.hbase.regionserver.RegionServerStoppedException;
35  import org.apache.hadoop.ipc.RemoteException;
36  import org.apache.hadoop.net.DNS;
37  
38  /**
39   * Retries scanner operations such as create, next, etc.
40   * Used by {@link ResultScanner}s made by {@link HTable}.
41   */
42  public class ScannerCallable extends ServerCallable<Result[]> {
43    public static final String LOG_SCANNER_LATENCY_CUTOFF
44      = "hbase.client.log.scanner.latency.cutoff";
45    public static final String LOG_SCANNER_ACTIVITY = "hbase.client.log.scanner.activity";
46    private static final Log LOG = LogFactory.getLog(ScannerCallable.class);
47    private long scannerId = -1L;
48    private boolean instantiated = false;
49    private boolean closed = false;
50    private Scan scan;
51    private int caching = 1;
52    private ScanMetrics scanMetrics;
53    private boolean logScannerActivity = false;
54    private int logCutOffLatency = 1000;
55  
56    // indicate if it is a remote server call
57    private boolean isRegionServerRemote = true;
58  
59    /**
60     * @param connection which connection
61     * @param tableName table callable is on
62     * @param scan the scan to execute
63     * @param scanMetrics the ScanMetrics to used, if it is null, ScannerCallable
64     * won't collect metrics
65     */
66    public ScannerCallable (HConnection connection, byte [] tableName, Scan scan,
67      ScanMetrics scanMetrics) {
68      super(connection, tableName, scan.getStartRow());
69      this.scan = scan;
70      this.scanMetrics = scanMetrics;
71      Configuration conf = connection.getConfiguration();
72      logScannerActivity = conf.getBoolean(LOG_SCANNER_ACTIVITY, false);
73      logCutOffLatency = conf.getInt(LOG_SCANNER_LATENCY_CUTOFF, 1000);
74    }
75  
76    /**
77     * @param reload force reload of server location
78     * @throws IOException
79     */
80    @Override
81    public void connect(boolean reload) throws IOException {
82      if (!instantiated || reload) {
83        super.connect(reload);
84        checkIfRegionServerIsRemote();
85        instantiated = true;
86      }
87  
88      // check how often we retry.
89      // HConnectionManager will call instantiateServer with reload==true
90      // if and only if for retries.
91      if (reload && this.scanMetrics != null) {
92        this.scanMetrics.countOfRPCRetries.inc();
93        if (isRegionServerRemote) {
94          this.scanMetrics.countOfRemoteRPCRetries.inc();
95        }
96      }
97    }
98  
99    /**
100    * compare the local machine hostname with region server's hostname
101    * to decide if hbase client connects to a remote region server
102    * @throws UnknownHostException.
103    */
104   private void checkIfRegionServerIsRemote() throws UnknownHostException {
105     String myAddress = DNS.getDefaultHost("default", "default");
106     if (this.location.getHostname().equalsIgnoreCase(myAddress)) {
107       isRegionServerRemote = false;
108     } else {
109       isRegionServerRemote = true;
110     }
111   }
112 
113   /**
114    * @see java.util.concurrent.Callable#call()
115    */
116   public Result [] call() throws IOException {
117     if (scannerId != -1L && closed) {
118       close();
119     } else if (scannerId == -1L && !closed) {
120       this.scannerId = openScanner();
121     } else {
122       Result [] rrs = null;
123       try {
124         incRPCcallsMetrics();
125         long timestamp = System.currentTimeMillis();
126         rrs = server.next(scannerId, caching);
127         if (logScannerActivity) {
128           long now = System.currentTimeMillis();
129           if (now - timestamp > logCutOffLatency) {
130             int rows = rrs == null ? 0 : rrs.length;
131             LOG.info("Took " + (now-timestamp) + "ms to fetch "
132               + rows + " rows from scanner=" + scannerId);
133           }
134         }
135         updateResultsMetrics(rrs);
136       } catch (IOException e) {
137         if (logScannerActivity) {
138           LOG.info("Got exception in fetching from scanner="
139             + scannerId, e);
140         }
141         IOException ioe = null;
142         if (e instanceof RemoteException) {
143           ioe = RemoteExceptionHandler.decodeRemoteException((RemoteException)e);
144         }
145         if (ioe == null) throw new IOException(e);
146         if (logScannerActivity && (ioe instanceof UnknownScannerException)) {
147           try {
148             HRegionLocation location =
149               connection.relocateRegion(tableName, scan.getStartRow());
150             LOG.info("Scanner=" + scannerId
151               + " expired, current region location is " + location.toString()
152               + " ip:" + location.getServerAddress().getBindAddress());
153           } catch (Throwable t) {
154             LOG.info("Failed to relocate region", t);
155           }
156         }
157         if (ioe instanceof NotServingRegionException) {
158           // Throw a DNRE so that we break out of cycle of calling NSRE
159           // when what we need is to open scanner against new location.
160           // Attach NSRE to signal client that it needs to resetup scanner.
161           if (this.scanMetrics != null) {
162             this.scanMetrics.countOfNSRE.inc();
163           }
164           throw new DoNotRetryIOException("Reset scanner", ioe);
165         } else if (ioe instanceof RegionServerStoppedException) {
166           // Throw a DNRE so that we break out of cycle of calling RSSE
167           // when what we need is to open scanner against new location.
168           // Attach RSSE to signal client that it needs to resetup scanner.
169           throw new DoNotRetryIOException("Reset scanner", ioe);
170         } else {
171           // The outer layers will retry
172           throw ioe;
173         }
174       }
175       return rrs;
176     }
177     return null;
178   }
179 
180   private void incRPCcallsMetrics() {
181     if (this.scanMetrics == null) {
182       return;
183     }
184     this.scanMetrics.countOfRPCcalls.inc();
185     if (isRegionServerRemote) {
186       this.scanMetrics.countOfRemoteRPCcalls.inc();
187     }
188   }
189 
190   private void updateResultsMetrics(Result[] rrs) {
191     if (this.scanMetrics == null || rrs == null) {
192       return;
193     }
194     for (Result rr : rrs) {
195       this.scanMetrics.countOfBytesInResults.inc(rr.getBytes().getLength());
196       if (isRegionServerRemote) {
197         this.scanMetrics.countOfBytesInRemoteResults.inc(
198           rr.getBytes().getLength());
199       }
200     }
201   }
202 
203   private void close() {
204     if (this.scannerId == -1L) {
205       return;
206     }
207     try {
208       incRPCcallsMetrics();
209       this.server.close(this.scannerId);
210     } catch (IOException e) {
211       LOG.warn("Ignore, probably already closed", e);
212     }
213     this.scannerId = -1L;
214   }
215 
216   protected long openScanner() throws IOException {
217     incRPCcallsMetrics();
218     long id = this.server.openScanner(this.location.getRegionInfo().getRegionName(),
219        this.scan);
220     if (logScannerActivity) {
221       LOG.info("Open scanner=" + id + " for scan=" + scan.toString()
222         + " on region " + this.location.toString() + " ip:"
223         + this.location.getServerAddress().getBindAddress());
224     }
225     return id;
226   }
227 
228   protected Scan getScan() {
229     return scan;
230   }
231 
232   /**
233    * Call this when the next invocation of call should close the scanner
234    */
235   public void setClose() {
236     this.closed = true;
237   }
238 
239   /**
240    * @return the HRegionInfo for the current region
241    */
242   public HRegionInfo getHRegionInfo() {
243     if (!instantiated) {
244       return null;
245     }
246     return location.getRegionInfo();
247   }
248 
249   /**
250    * Get the number of rows that will be fetched on next
251    * @return the number of rows for caching
252    */
253   public int getCaching() {
254     return caching;
255   }
256 
257   /**
258    * Set the number of rows that will be fetched on next
259    * @param caching the number of rows for caching
260    */
261   public void setCaching(int caching) {
262     this.caching = caching;
263   }
264 }