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