View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  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,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.mapred;
20  
21  import static org.apache.hadoop.hbase.mapreduce.TableRecordReaderImpl.LOG_PER_ROW_COUNT;
22  
23  import java.io.IOException;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.apache.hadoop.conf.Configuration;
28  import org.apache.hadoop.hbase.classification.InterfaceAudience;
29  import org.apache.hadoop.hbase.classification.InterfaceStability;
30  import org.apache.hadoop.hbase.client.HTable;
31  import org.apache.hadoop.hbase.client.Result;
32  import org.apache.hadoop.hbase.client.ResultScanner;
33  import org.apache.hadoop.hbase.client.Scan;
34  import org.apache.hadoop.hbase.client.ScannerCallable;
35  import org.apache.hadoop.hbase.client.Table;
36  import org.apache.hadoop.hbase.DoNotRetryIOException;
37  import org.apache.hadoop.hbase.filter.Filter;
38  import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
39  import org.apache.hadoop.hbase.mapreduce.TableInputFormat;
40  import org.apache.hadoop.hbase.util.Bytes;
41  import org.apache.hadoop.util.StringUtils;
42  
43  /**
44   * Iterate over an HBase table data, return (Text, RowResult) pairs
45   */
46  @InterfaceAudience.Public
47  @InterfaceStability.Stable
48  public class TableRecordReaderImpl {
49    private static final Log LOG = LogFactory.getLog(TableRecordReaderImpl.class);
50  
51    private byte [] startRow;
52    private byte [] endRow;
53    private byte [] lastSuccessfulRow;
54    private Filter trrRowFilter;
55    private ResultScanner scanner;
56    private Table htable;
57    private byte [][] trrInputColumns;
58    private long timestamp;
59    private int rowcount;
60    private boolean logScannerActivity = false;
61    private int logPerRowCount = 100;
62  
63    /**
64     * Restart from survivable exceptions by creating a new scanner.
65     *
66     * @param firstRow
67     * @throws IOException
68     */
69    public void restart(byte[] firstRow) throws IOException {
70      Scan currentScan;
71      if ((endRow != null) && (endRow.length > 0)) {
72        if (trrRowFilter != null) {
73          Scan scan = new Scan(firstRow, endRow);
74          TableInputFormat.addColumns(scan, trrInputColumns);
75          scan.setFilter(trrRowFilter);
76          scan.setCacheBlocks(false);
77          this.scanner = this.htable.getScanner(scan);
78          currentScan = scan;
79        } else {
80          LOG.debug("TIFB.restart, firstRow: " +
81              Bytes.toStringBinary(firstRow) + ", endRow: " +
82              Bytes.toStringBinary(endRow));
83          Scan scan = new Scan(firstRow, endRow);
84          TableInputFormat.addColumns(scan, trrInputColumns);
85          this.scanner = this.htable.getScanner(scan);
86          currentScan = scan;
87        }
88      } else {
89        LOG.debug("TIFB.restart, firstRow: " +
90            Bytes.toStringBinary(firstRow) + ", no endRow");
91  
92        Scan scan = new Scan(firstRow);
93        TableInputFormat.addColumns(scan, trrInputColumns);
94        scan.setFilter(trrRowFilter);
95        this.scanner = this.htable.getScanner(scan);
96        currentScan = scan;
97      }
98      if (logScannerActivity) {
99        LOG.info("Current scan=" + currentScan.toString());
100       timestamp = System.currentTimeMillis();
101       rowcount = 0;
102     }
103   }
104 
105   /**
106    * Build the scanner. Not done in constructor to allow for extension.
107    *
108    * @throws IOException
109    */
110   public void init() throws IOException {
111     restart(startRow);
112   }
113 
114   byte[] getStartRow() {
115     return this.startRow;
116   }
117   /**
118    * @param htable the {@link org.apache.hadoop.hbase.HTableDescriptor} to scan.
119    */
120   public void setHTable(Table htable) {
121     Configuration conf = htable.getConfiguration();
122     logScannerActivity = conf.getBoolean(
123       ScannerCallable.LOG_SCANNER_ACTIVITY, false);
124     logPerRowCount = conf.getInt(LOG_PER_ROW_COUNT, 100);
125     this.htable = htable;
126   }
127 
128   /**
129    * @param htable the {@link org.apache.hadoop.hbase.HTableDescriptor} to scan.
130    * @deprecated Use {@link #setHTable(org.apache.hadoop.hbase.client.Table)} instead.
131    */
132   @Deprecated
133   public void setHTable(HTable htable) {
134     setHTable((Table)htable);
135   }
136 
137   /**
138    * @param inputColumns the columns to be placed in {@link Result}.
139    */
140   public void setInputColumns(final byte [][] inputColumns) {
141     this.trrInputColumns = inputColumns;
142   }
143 
144   /**
145    * @param startRow the first row in the split
146    */
147   public void setStartRow(final byte [] startRow) {
148     this.startRow = startRow;
149   }
150 
151   /**
152    *
153    * @param endRow the last row in the split
154    */
155   public void setEndRow(final byte [] endRow) {
156     this.endRow = endRow;
157   }
158 
159   /**
160    * @param rowFilter the {@link Filter} to be used.
161    */
162   public void setRowFilter(Filter rowFilter) {
163     this.trrRowFilter = rowFilter;
164   }
165 
166   public void close() {
167     this.scanner.close();
168     try {
169       this.htable.close();
170     } catch (IOException ioe) {
171       LOG.warn("Error closing table", ioe);
172     }
173   }
174 
175   /**
176    * @return ImmutableBytesWritable
177    *
178    * @see org.apache.hadoop.mapred.RecordReader#createKey()
179    */
180   public ImmutableBytesWritable createKey() {
181     return new ImmutableBytesWritable();
182   }
183 
184   /**
185    * @return RowResult
186    *
187    * @see org.apache.hadoop.mapred.RecordReader#createValue()
188    */
189   public Result createValue() {
190     return new Result();
191   }
192 
193   public long getPos() {
194     // This should be the ordinal tuple in the range;
195     // not clear how to calculate...
196     return 0;
197   }
198 
199   public float getProgress() {
200     // Depends on the total number of tuples and getPos
201     return 0;
202   }
203 
204   /**
205    * @param key HStoreKey as input key.
206    * @param value MapWritable as input value
207    * @return true if there was more data
208    * @throws IOException
209    */
210   public boolean next(ImmutableBytesWritable key, Result value)
211   throws IOException {
212     Result result;
213     try {
214       try {
215         result = this.scanner.next();
216         if (logScannerActivity) {
217           rowcount ++;
218           if (rowcount >= logPerRowCount) {
219             long now = System.currentTimeMillis();
220             LOG.info("Mapper took " + (now-timestamp)
221               + "ms to process " + rowcount + " rows");
222             timestamp = now;
223             rowcount = 0;
224           }
225         }
226       } catch (IOException e) {
227         // do not retry if the exception tells us not to do so
228         if (e instanceof DoNotRetryIOException) {
229           throw e;
230         }
231         // try to handle all other IOExceptions by restarting
232         // the scanner, if the second call fails, it will be rethrown
233         LOG.debug("recovered from " + StringUtils.stringifyException(e));
234         if (lastSuccessfulRow == null) {
235           LOG.warn("We are restarting the first next() invocation," +
236               " if your mapper has restarted a few other times like this" +
237               " then you should consider killing this job and investigate" +
238               " why it's taking so long.");
239         }
240         if (lastSuccessfulRow == null) {
241           restart(startRow);
242         } else {
243           restart(lastSuccessfulRow);
244           this.scanner.next();    // skip presumed already mapped row
245         }
246         result = this.scanner.next();
247       }
248 
249       if (result != null && result.size() > 0) {
250         key.set(result.getRow());
251         lastSuccessfulRow = key.get();
252         value.copyFrom(result);
253         return true;
254       }
255       return false;
256     } catch (IOException ioe) {
257       if (logScannerActivity) {
258         long now = System.currentTimeMillis();
259         LOG.info("Mapper took " + (now-timestamp)
260           + "ms to process " + rowcount + " rows");
261         LOG.info(ioe);
262         String lastRow = lastSuccessfulRow == null ?
263           "null" : Bytes.toStringBinary(lastSuccessfulRow);
264         LOG.info("lastSuccessfulRow=" + lastRow);
265       }
266       throw ioe;
267     }
268   }
269 }