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.replication.regionserver;
20  
21  import java.io.Closeable;
22  import java.io.FileNotFoundException;
23  import java.io.IOException;
24  import java.util.Iterator;
25  import java.util.NoSuchElementException;
26  import java.util.concurrent.PriorityBlockingQueue;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.hadoop.conf.Configuration;
31  import org.apache.hadoop.fs.FileStatus;
32  import org.apache.hadoop.fs.FileSystem;
33  import org.apache.hadoop.fs.Path;
34  import org.apache.hadoop.hbase.HConstants;
35  import org.apache.hadoop.hbase.classification.InterfaceAudience;
36  import org.apache.hadoop.hbase.classification.InterfaceStability;
37  import org.apache.hadoop.hbase.regionserver.wal.ProtobufLogReader;
38  import org.apache.hadoop.hbase.util.CancelableProgressable;
39  import org.apache.hadoop.hbase.util.FSUtils;
40  import org.apache.hadoop.hbase.util.LeaseNotRecoveredException;
41  import org.apache.hadoop.hbase.wal.WAL.Entry;
42  import org.apache.hadoop.hbase.wal.WAL.Reader;
43  import org.apache.hadoop.hbase.wal.WALFactory;
44  import org.apache.hadoop.ipc.RemoteException;
45  
46  /**
47   * Streaming access to WAL entries. This class is given a queue of WAL {@link Path}, and continually
48   * iterates through all the WAL {@link Entry} in the queue. When it's done reading from a Path, it
49   * dequeues it and starts reading from the next.
50   */
51  @InterfaceAudience.Private
52  @InterfaceStability.Evolving
53  public class WALEntryStream implements Iterator<Entry>, Closeable, Iterable<Entry> {
54    private static final Log LOG = LogFactory.getLog(WALEntryStream.class);
55  
56    private Reader reader;
57    private Path currentPath;
58    // cache of next entry for hasNext()
59    private Entry currentEntry;
60    // position after reading current entry
61    private long currentPosition = 0;
62    private final ReplicationSourceLogQueue logQueue;
63    private final String walGroupId;
64    private FileSystem fs;
65    private Configuration conf;
66    private MetricsSource metrics;
67  
68    /**
69     * Create an entry stream over the given queue
70     * @param logQueue the queue of WAL paths
71     * @param fs {@link FileSystem} to use to create {@link Reader} for this stream
72     * @param conf {@link Configuration} to use to create {@link Reader} for this stream
73     * @param metrics replication metrics
74     * @param walGroupId wal prefix
75     */
76    public WALEntryStream(ReplicationSourceLogQueue logQueue, FileSystem fs, Configuration conf,
77        MetricsSource metrics, String walGroupId) {
78      this(logQueue, fs, conf, 0, metrics, walGroupId);
79    }
80  
81    /**
82     * Create an entry stream over the given queue at the given start position
83     * @param logQueue the queue of WAL paths
84     * @param fs {@link FileSystem} to use to create {@link Reader} for this stream
85     * @param conf the {@link Configuration} to use to create {@link Reader} for this stream
86     * @param startPosition the position in the first WAL to start reading at
87     * @param metrics the replication metrics
88     * @param walGroupId wal prefix
89     */
90    public WALEntryStream(ReplicationSourceLogQueue logQueue, FileSystem fs, Configuration conf,
91        long startPosition, MetricsSource metrics, String walGroupId) {
92      this.logQueue = logQueue;
93      this.fs = fs;
94      this.conf = conf;
95      this.currentPosition = startPosition;
96      this.metrics = metrics;
97      this.walGroupId = walGroupId;
98    }
99  
100   /**
101    * @return true if there is another WAL {@link Entry}
102    * @throws WALEntryStreamRuntimeException if there was an Exception while reading
103    */
104   @Override
105   public boolean hasNext() {
106     if (currentEntry == null) {
107       try {
108         tryAdvanceEntry();
109       } catch (Exception e) {
110         throw new WALEntryStreamRuntimeException(e);
111       }
112     }
113     return currentEntry != null;
114   }
115 
116   /**
117    * @return the next WAL entry in this stream
118    * @throws WALEntryStreamRuntimeException if there was an IOException
119    * @throws NoSuchElementException if no more entries in the stream.
120    */
121   @Override
122   public Entry next() {
123     if (!hasNext()) {
124       throw new NoSuchElementException();
125     }
126     Entry save = currentEntry;
127     currentEntry = null; // gets reloaded by hasNext()
128     return save;
129   }
130 
131   /**
132    * Not supported.
133    */
134   @Override
135   public void remove() {
136     throw new UnsupportedOperationException();
137   }
138 
139   /**
140    * {@inheritDoc}
141    */
142   @Override
143   public void close() throws IOException {
144     closeReader();
145   }
146 
147   /**
148    * @return the iterator over WAL entries in the queue.
149    */
150   @Override
151   public Iterator<Entry> iterator() {
152     return this;
153   }
154 
155   /**
156    * @return the position of the last Entry returned by next()
157    */
158   public long getPosition() {
159     return currentPosition;
160   }
161 
162   /**
163    * @return the {@link Path} of the current WAL
164    */
165   public Path getCurrentPath() {
166     return currentPath;
167   }
168 
169   private String getCurrentPathStat() {
170     StringBuilder sb = new StringBuilder();
171     if (currentPath != null) {
172       sb.append("currently replicating from: ").append(currentPath).append(" at position: ")
173           .append(currentPosition).append("\n");
174     } else {
175       sb.append("no replication ongoing, waiting for new log");
176     }
177     return sb.toString();
178   }
179 
180   /**
181    * Should be called if the stream is to be reused (i.e. used again after hasNext() has returned
182    * false)
183    * @throws IOException io exception while resetting the reader
184    */
185   public void reset() throws IOException {
186     if (reader != null && currentPath != null) {
187       resetReader();
188     }
189   }
190 
191   private void setPosition(long position) {
192     currentPosition = position;
193   }
194 
195   private void setCurrentPath(Path path) {
196     this.currentPath = path;
197   }
198 
199   private void tryAdvanceEntry() throws IOException {
200     if (checkReader()) {
201       readNextEntryAndSetPosition();
202       if (currentEntry == null) { // no more entries in this log file - see if log was rolled
203         if (logQueue.getQueue(walGroupId).size() > 1) { // log was rolled
204           // Before dequeueing, we should always get one more attempt at reading.
205           // This is in case more entries came in after we opened the reader,
206           // and a new log was enqueued while we were reading. See HBASE-6758
207           resetReader();
208           readNextEntryAndSetPosition();
209           if (currentEntry == null) {
210             if (checkAllBytesParsed()) { // now we're certain we're done with this log file
211               dequeueCurrentLog();
212               if (openNextLog()) {
213                 readNextEntryAndSetPosition();
214               }
215             }
216           }
217         } // no other logs, we've simply hit the end of the current open log. Do nothing
218       }
219     }
220     // do nothing if we don't have a WAL Reader (e.g. if there's no logs in queue)
221   }
222 
223   // HBASE-15984 check to see we have in fact parsed all data in a cleanly closed file
224   private boolean checkAllBytesParsed() throws IOException {
225     // -1 means the wal wasn't closed cleanly.
226     final long trailerSize = currentTrailerSize();
227     FileStatus stat = null;
228     try {
229       stat = fs.getFileStatus(this.currentPath);
230     } catch (IOException exception) {
231       LOG.warn("Couldn't get file length information about log " + this.currentPath + ", it "
232           + (trailerSize < 0 ? "was not" : "was") + " closed cleanly " + getCurrentPathStat());
233       metrics.incrUnknownFileLengthForClosedWAL();
234     }
235     if (stat != null) {
236       if (trailerSize < 0) {
237         if (currentPosition < stat.getLen()) {
238           final long skippedBytes = stat.getLen() - currentPosition;
239           if (LOG.isDebugEnabled()) {
240             LOG.debug("Reached the end of WAL file '" + currentPath
241                 + "'. It was not closed cleanly, so we did not parse " + skippedBytes
242                 + " bytes of data. This is normally ok.");
243           }
244           metrics.incrUncleanlyClosedWALs();
245           metrics.incrBytesSkippedInUncleanlyClosedWALs(skippedBytes);
246         }
247       } else if (currentPosition + trailerSize < stat.getLen()) {
248         LOG.warn("Processing end of WAL file '" + currentPath + "'. At position " + currentPosition
249             + ", which is too far away from reported file length " + stat.getLen()
250             + ". Restarting WAL reading (see HBASE-15983 for details). " + getCurrentPathStat());
251         setPosition(0);
252         resetReader();
253         metrics.incrRestartedWALReading();
254         metrics.incrRepeatedFileBytes(currentPosition);
255         return false;
256       }
257     }
258     if (LOG.isTraceEnabled()) {
259       LOG.trace("Reached the end of log " + this.currentPath + ", and the length of the file is "
260           + (stat == null ? "N/A" : stat.getLen()));
261     }
262     metrics.incrCompletedWAL();
263     return true;
264   }
265 
266   private void dequeueCurrentLog() throws IOException {
267     if (LOG.isDebugEnabled()) {
268       LOG.debug("Reached the end of log " + currentPath);
269     }
270     closeReader();
271     logQueue.remove(walGroupId);
272     setCurrentPath(null);
273     setPosition(0);
274   }
275 
276   private void readNextEntryAndSetPosition() throws IOException {
277     Entry readEntry = reader.next();
278     long readerPos = reader.getPosition();
279     if (readEntry != null) {
280       metrics.incrLogEditsRead();
281       metrics.incrLogReadInBytes(readerPos - currentPosition);
282     }
283     currentEntry = readEntry; // could be null
284     setPosition(readerPos);
285   }
286 
287   private void closeReader() throws IOException {
288     if (reader != null) {
289       reader.close();
290       reader = null;
291     }
292   }
293 
294   // if we don't have a reader, open a reader on the next log
295   private boolean checkReader() throws IOException {
296     if (reader == null) {
297       return openNextLog();
298     }
299     return true;
300   }
301 
302   // open a reader on the next log in queue
303   private boolean openNextLog() throws IOException {
304     PriorityBlockingQueue<Path> queue = logQueue.getQueue(walGroupId);
305     Path nextPath = queue.peek();
306     if (nextPath != null) {
307       openReader(nextPath);
308       if (reader != null) {
309         return true;
310       }
311     }
312     return false;
313   }
314 
315   Path getArchivedLog(Path path) throws IOException {
316     Path rootDir = FSUtils.getRootDir(conf);
317     Path oldLogDir = new Path(rootDir, HConstants.HREGION_OLDLOGDIR_NAME);
318     Path archivedLogLocation = new Path(oldLogDir, path.getName());
319     if (fs.exists(archivedLogLocation)) {
320       LOG.info("Log " + path + " was moved to " + archivedLogLocation);
321       return archivedLogLocation;
322     } else {
323       LOG.error("Couldn't locate log: " + path);
324       return path;
325     }
326   }
327 
328   private void handleFileNotFound(Path path, FileNotFoundException fnfe) throws IOException {
329     // If the log was archived, continue reading from there
330     Path archivedLog = getArchivedLog(path);
331     if (!path.equals(archivedLog)) {
332       openReader(archivedLog);
333     } else {
334       throw fnfe;
335     }
336   }
337   private void openReader(Path path) throws IOException {
338     try {
339       // Detect if this is a new file, if so get a new reader else
340       // reset the current reader so that we see the new data
341       if (reader == null || !getCurrentPath().equals(path)) {
342         closeReader();
343         reader = WALFactory.createReader(fs, path, conf);
344         seek();
345         setCurrentPath(path);
346       } else {
347         resetReader();
348       }
349     } catch (FileNotFoundException fnfe) {
350       handleFileNotFound(path, fnfe);
351     }  catch (RemoteException re) {
352       IOException ioe = re.unwrapRemoteException(FileNotFoundException.class);
353       if (!(ioe instanceof FileNotFoundException)) {
354         throw ioe;
355       }
356       handleFileNotFound(path, (FileNotFoundException)ioe);
357     } catch (LeaseNotRecoveredException lnre) {
358       // HBASE-15019 the WAL was not closed due to some hiccup.
359       LOG.warn("Try to recover the WAL lease " + path, lnre);
360       recoverLease(conf, path);
361       reader = null;
362     } catch (NullPointerException npe) {
363       // Workaround for race condition in HDFS-4380
364       // which throws a NPE if we open a file before any data node has the most recent block
365       // Just sleep and retry. Will require re-reading compressed WALs for compressionContext.
366       LOG.warn("Got NPE opening reader, will retry.");
367       reader = null;
368     }
369   }
370 
371   // For HBASE-15019
372   private void recoverLease(final Configuration conf, final Path path) {
373     try {
374       final FileSystem dfs = FSUtils.getCurrentFileSystem(conf);
375       FSUtils fsUtils = FSUtils.getInstance(dfs, conf);
376       fsUtils.recoverFileLease(dfs, path, conf, new CancelableProgressable() {
377         @Override
378         public boolean progress() {
379           LOG.debug("recover WAL lease: " + path);
380           return true;
381         }
382       });
383     } catch (IOException e) {
384       LOG.warn("unable to recover lease for WAL: " + path, e);
385     }
386   }
387 
388   private void resetReader() throws IOException {
389     try {
390       reader.reset();
391       seek();
392     } catch (FileNotFoundException fnfe) {
393       // If the log was archived, continue reading from there
394       Path archivedLog = getArchivedLog(currentPath);
395       if (!currentPath.equals(archivedLog)) {
396         openReader(archivedLog);
397       } else {
398         throw fnfe;
399       }
400     } catch (NullPointerException npe) {
401       throw new IOException("NPE resetting reader, likely HDFS-4380", npe);
402     }
403   }
404 
405   private void seek() throws IOException {
406     if (currentPosition != 0) {
407       reader.seek(currentPosition);
408     }
409   }
410 
411   private long currentTrailerSize() {
412     long size = -1L;
413     if (reader instanceof ProtobufLogReader) {
414       final ProtobufLogReader pblr = (ProtobufLogReader) reader;
415       size = pblr.trailerSize();
416     }
417     return size;
418   }
419 
420   @InterfaceAudience.Private
421   public static class WALEntryStreamRuntimeException extends RuntimeException {
422     private static final long serialVersionUID = -6298201811259982568L;
423 
424     public WALEntryStreamRuntimeException(Exception e) {
425       super(e);
426     }
427   }
428 
429 }