org.apache.hadoop.hbase.replication.regionserver.WALEntryStream.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.hbase.replication.regionserver.WALEntryStream.java

Source

/**
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.hadoop.hbase.replication.regionserver;

import java.io.Closeable;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.PriorityBlockingQueue;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.classification.InterfaceAudience;
import org.apache.hadoop.hbase.classification.InterfaceStability;
import org.apache.hadoop.hbase.regionserver.wal.ProtobufLogReader;
import org.apache.hadoop.hbase.util.CancelableProgressable;
import org.apache.hadoop.hbase.util.FSUtils;
import org.apache.hadoop.hbase.util.LeaseNotRecoveredException;
import org.apache.hadoop.hbase.wal.WAL.Entry;
import org.apache.hadoop.hbase.wal.WAL.Reader;
import org.apache.hadoop.hbase.wal.WALFactory;

/**
 * Streaming access to WAL entries. This class is given a queue of WAL {@link Path}, and continually
 * iterates through all the WAL {@link Entry} in the queue. When it's done reading from a Path, it
 * dequeues it and starts reading from the next.
 */
@InterfaceAudience.Private
@InterfaceStability.Evolving
public class WALEntryStream implements Iterator<Entry>, Closeable, Iterable<Entry> {
    private static final Log LOG = LogFactory.getLog(WALEntryStream.class);

    private Reader reader;
    private Path currentPath;
    // cache of next entry for hasNext()
    private Entry currentEntry;
    // position after reading current entry
    private long currentPosition = 0;
    private PriorityBlockingQueue<Path> logQueue;
    private FileSystem fs;
    private Configuration conf;
    private MetricsSource metrics;

    /**
     * Create an entry stream over the given queue
     * @param logQueue the queue of WAL paths
     * @param fs {@link FileSystem} to use to create {@link Reader} for this stream
     * @param conf {@link Configuration} to use to create {@link Reader} for this stream
     * @param metrics replication metrics
     * @throws IOException
     */
    public WALEntryStream(PriorityBlockingQueue<Path> logQueue, FileSystem fs, Configuration conf,
            MetricsSource metrics) throws IOException {
        this(logQueue, fs, conf, 0, metrics);
    }

    /**
     * Create an entry stream over the given queue at the given start position
     * @param logQueue the queue of WAL paths
     * @param fs {@link FileSystem} to use to create {@link Reader} for this stream
     * @param conf {@link Configuration} to use to create {@link Reader} for this stream
     * @param startPosition the position in the first WAL to start reading at
     * @param metrics replication metrics
     * @throws IOException
     */
    public WALEntryStream(PriorityBlockingQueue<Path> logQueue, FileSystem fs, Configuration conf,
            long startPosition, MetricsSource metrics) throws IOException {
        this.logQueue = logQueue;
        this.fs = fs;
        this.conf = conf;
        this.currentPosition = startPosition;
        this.metrics = metrics;
    }

    /**
     * @return true if there is another WAL {@link Entry}
     * @throws WALEntryStreamRuntimeException if there was an Exception while reading
     */
    @Override
    public boolean hasNext() {
        if (currentEntry == null) {
            try {
                tryAdvanceEntry();
            } catch (Exception e) {
                throw new WALEntryStreamRuntimeException(e);
            }
        }
        return currentEntry != null;
    }

    /**
     * @return the next WAL entry in this stream
     * @throws WALEntryStreamRuntimeException if there was an IOException
     * @throws NoSuchElementException if no more entries in the stream.
     */
    @Override
    public Entry next() {
        if (!hasNext())
            throw new NoSuchElementException();
        Entry save = currentEntry;
        currentEntry = null; // gets reloaded by hasNext()
        return save;
    }

    /**
     * Not supported.
     */
    @Override
    public void remove() {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void close() throws IOException {
        closeReader();
    }

    /**
     * @return the iterator over WAL entries in the queue.
     */
    @Override
    public Iterator<Entry> iterator() {
        return this;
    }

    /**
     * @return the position of the last Entry returned by next()
     */
    public long getPosition() {
        return currentPosition;
    }

    /**
     * @return the {@link Path} of the current WAL
     */
    public Path getCurrentPath() {
        return currentPath;
    }

    private String getCurrentPathStat() {
        StringBuilder sb = new StringBuilder();
        if (currentPath != null) {
            sb.append("currently replicating from: ").append(currentPath).append(" at position: ")
                    .append(currentPosition).append("\n");
        } else {
            sb.append("no replication ongoing, waiting for new log");
        }
        return sb.toString();
    }

    /**
     * Should be called if the stream is to be reused (i.e. used again after hasNext() has returned
     * false)
     * @throws IOException
     */
    public void reset() throws IOException {
        if (reader != null && currentPath != null) {
            resetReader();
        }
    }

    private void setPosition(long position) {
        currentPosition = position;
    }

    private void setCurrentPath(Path path) {
        this.currentPath = path;
    }

    private void tryAdvanceEntry() throws IOException {
        if (checkReader()) {
            readNextEntryAndSetPosition();
            if (currentEntry == null) { // no more entries in this log file - see if log was rolled
                if (logQueue.size() > 1) { // log was rolled
                    // Before dequeueing, we should always get one more attempt at reading.
                    // This is in case more entries came in after we opened the reader,
                    // and a new log was enqueued while we were reading. See HBASE-6758
                    resetReader();
                    readNextEntryAndSetPosition();
                    if (currentEntry == null) {
                        if (checkAllBytesParsed()) { // now we're certain we're done with this log file
                            dequeueCurrentLog();
                            if (openNextLog()) {
                                readNextEntryAndSetPosition();
                            }
                        }
                    }
                } // no other logs, we've simply hit the end of the current open log. Do nothing
            }
        }
        // do nothing if we don't have a WAL Reader (e.g. if there's no logs in queue)
    }

    // HBASE-15984 check to see we have in fact parsed all data in a cleanly closed file
    private boolean checkAllBytesParsed() throws IOException {
        // -1 means the wal wasn't closed cleanly.
        final long trailerSize = currentTrailerSize();
        FileStatus stat = null;
        try {
            stat = fs.getFileStatus(this.currentPath);
        } catch (IOException exception) {
            LOG.warn("Couldn't get file length information about log " + this.currentPath + ", it "
                    + (trailerSize < 0 ? "was not" : "was") + " closed cleanly " + getCurrentPathStat());
            metrics.incrUnknownFileLengthForClosedWAL();
        }
        if (stat != null) {
            if (trailerSize < 0) {
                if (currentPosition < stat.getLen()) {
                    final long skippedBytes = stat.getLen() - currentPosition;
                    LOG.info("Reached the end of WAL file '" + currentPath
                            + "'. It was not closed cleanly, so we did not parse " + skippedBytes
                            + " bytes of data.");
                    metrics.incrUncleanlyClosedWALs();
                    metrics.incrBytesSkippedInUncleanlyClosedWALs(skippedBytes);
                }
            } else if (currentPosition + trailerSize < stat.getLen()) {
                LOG.warn("Processing end of WAL file '" + currentPath + "'. At position " + currentPosition
                        + ", which is too far away from reported file length " + stat.getLen()
                        + ". Restarting WAL reading (see HBASE-15983 for details). " + getCurrentPathStat());
                setPosition(0);
                resetReader();
                metrics.incrRestartedWALReading();
                metrics.incrRepeatedFileBytes(currentPosition);
                return false;
            }
        }
        if (LOG.isTraceEnabled()) {
            LOG.trace("Reached the end of log " + this.currentPath + ", and the length of the file is "
                    + (stat == null ? "N/A" : stat.getLen()));
        }
        metrics.incrCompletedWAL();
        return true;
    }

    private void dequeueCurrentLog() throws IOException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Reached the end of log " + currentPath);
        }
        closeReader();
        logQueue.remove();
        setPosition(0);
        metrics.decrSizeOfLogQueue();
    }

    private void readNextEntryAndSetPosition() throws IOException {
        Entry readEntry = reader.next();
        long readerPos = reader.getPosition();
        if (readEntry != null) {
            metrics.incrLogEditsRead();
            metrics.incrLogReadInBytes(readerPos - currentPosition);
        }
        currentEntry = readEntry; // could be null
        setPosition(readerPos);
    }

    private void closeReader() throws IOException {
        if (reader != null) {
            reader.close();
            reader = null;
        }
    }

    // if we don't have a reader, open a reader on the next log
    private boolean checkReader() throws IOException {
        if (reader == null) {
            return openNextLog();
        }
        return true;
    }

    // open a reader on the next log in queue
    private boolean openNextLog() throws IOException {
        Path nextPath = logQueue.peek();
        if (nextPath != null) {
            openReader(nextPath);
            if (reader != null)
                return true;
        }
        return false;
    }

    private Path getArchivedLog(Path path) throws IOException {
        Path rootDir = FSUtils.getRootDir(conf);
        Path oldLogDir = new Path(rootDir, HConstants.HREGION_OLDLOGDIR_NAME);
        Path archivedLogLocation = new Path(oldLogDir, path.getName());
        if (fs.exists(archivedLogLocation)) {
            LOG.info("Log " + path + " was moved to " + archivedLogLocation);
            return archivedLogLocation;
        } else {
            LOG.error("Couldn't locate log: " + path);
            return path;
        }
    }

    private void openReader(Path path) throws IOException {
        try {
            // Detect if this is a new file, if so get a new reader else
            // reset the current reader so that we see the new data
            if (reader == null || !getCurrentPath().equals(path)) {
                closeReader();
                reader = WALFactory.createReader(fs, path, conf);
                seek();
                setCurrentPath(path);
            } else {
                resetReader();
            }
        } catch (FileNotFoundException fnfe) {
            // If the log was archived, continue reading from there
            Path archivedLog = getArchivedLog(path);
            if (!path.equals(archivedLog)) {
                openReader(archivedLog);
            } else {
                throw fnfe;
            }
        } catch (LeaseNotRecoveredException lnre) {
            // HBASE-15019 the WAL was not closed due to some hiccup.
            LOG.warn("Try to recover the WAL lease " + currentPath, lnre);
            recoverLease(conf, currentPath);
            reader = null;
        } catch (NullPointerException npe) {
            // Workaround for race condition in HDFS-4380
            // which throws a NPE if we open a file before any data node has the most recent block
            // Just sleep and retry. Will require re-reading compressed WALs for compressionContext.
            LOG.warn("Got NPE opening reader, will retry.");
            reader = null;
        }
    }

    // For HBASE-15019
    private void recoverLease(final Configuration conf, final Path path) {
        try {
            final FileSystem dfs = FSUtils.getCurrentFileSystem(conf);
            FSUtils fsUtils = FSUtils.getInstance(dfs, conf);
            fsUtils.recoverFileLease(dfs, path, conf, new CancelableProgressable() {
                @Override
                public boolean progress() {
                    LOG.debug("recover WAL lease: " + path);
                    return true;
                }
            });
        } catch (IOException e) {
            LOG.warn("unable to recover lease for WAL: " + path, e);
        }
    }

    private void resetReader() throws IOException {
        try {
            reader.reset();
            seek();
        } catch (FileNotFoundException fnfe) {
            // If the log was archived, continue reading from there
            Path archivedLog = getArchivedLog(currentPath);
            if (!currentPath.equals(archivedLog)) {
                openReader(archivedLog);
            } else {
                throw fnfe;
            }
        } catch (NullPointerException npe) {
            throw new IOException("NPE resetting reader, likely HDFS-4380", npe);
        }
    }

    private void seek() throws IOException {
        if (currentPosition != 0) {
            reader.seek(currentPosition);
        }
    }

    private long currentTrailerSize() {
        long size = -1L;
        if (reader instanceof ProtobufLogReader) {
            final ProtobufLogReader pblr = (ProtobufLogReader) reader;
            size = pblr.trailerSize();
        }
        return size;
    }

    @InterfaceAudience.Private
    public static class WALEntryStreamRuntimeException extends RuntimeException {
        private static final long serialVersionUID = -6298201811259982568L;

        public WALEntryStreamRuntimeException(Exception e) {
            super(e);
        }
    }

}