org.apache.flume.channel.file.CheckpointRebuilder.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.flume.channel.file.CheckpointRebuilder.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.flume.channel.file;

import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;

import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CheckpointRebuilder {

    private final List<File> logFiles;
    private final FlumeEventQueue queue;
    private final Set<ComparableFlumeEventPointer> committedPuts = Sets.newHashSet();
    private final Set<ComparableFlumeEventPointer> pendingTakes = Sets.newHashSet();
    private final SetMultimap<Long, ComparableFlumeEventPointer> uncommittedPuts = HashMultimap.create();
    private final SetMultimap<Long, ComparableFlumeEventPointer> uncommittedTakes = HashMultimap.create();

    private static Logger LOG = LoggerFactory.getLogger(CheckpointRebuilder.class);

    public CheckpointRebuilder(List<File> logFiles, FlumeEventQueue queue) throws IOException {
        this.logFiles = logFiles;
        this.queue = queue;
    }

    public boolean rebuild() throws IOException, Exception {
        LOG.info("Attempting to fast replay the log files.");
        List<LogFile.SequentialReader> logReaders = Lists.newArrayList();
        for (File logFile : logFiles) {
            try {
                logReaders.add(LogFileFactory.getSequentialReader(logFile, null));
            } catch (EOFException e) {
                LOG.warn("Ignoring " + logFile + " due to EOF", e);
            }
        }
        long transactionIDSeed = 0;
        long writeOrderIDSeed = 0;
        try {
            for (LogFile.SequentialReader log : logReaders) {
                LogRecord entry;
                int fileID = log.getLogFileID();
                while ((entry = log.next()) != null) {
                    int offset = entry.getOffset();
                    TransactionEventRecord record = entry.getEvent();
                    long trans = record.getTransactionID();
                    long writeOrderID = record.getLogWriteOrderID();
                    transactionIDSeed = Math.max(trans, transactionIDSeed);
                    writeOrderIDSeed = Math.max(writeOrderID, writeOrderIDSeed);
                    if (record.getRecordType() == TransactionEventRecord.Type.PUT.get()) {
                        uncommittedPuts.put(record.getTransactionID(), new ComparableFlumeEventPointer(
                                new FlumeEventPointer(fileID, offset), record.getLogWriteOrderID()));
                    } else if (record.getRecordType() == TransactionEventRecord.Type.TAKE.get()) {
                        Take take = (Take) record;
                        uncommittedTakes.put(record.getTransactionID(),
                                new ComparableFlumeEventPointer(
                                        new FlumeEventPointer(take.getFileID(), take.getOffset()),
                                        record.getLogWriteOrderID()));
                    } else if (record.getRecordType() == TransactionEventRecord.Type.COMMIT.get()) {
                        Commit commit = (Commit) record;
                        if (commit.getType() == TransactionEventRecord.Type.PUT.get()) {
                            Set<ComparableFlumeEventPointer> puts = uncommittedPuts.get(record.getTransactionID());
                            if (puts != null) {
                                for (ComparableFlumeEventPointer put : puts) {
                                    if (!pendingTakes.remove(put)) {
                                        committedPuts.add(put);
                                    }
                                }
                            }
                        } else {
                            Set<ComparableFlumeEventPointer> takes = uncommittedTakes
                                    .get(record.getTransactionID());
                            if (takes != null) {
                                for (ComparableFlumeEventPointer take : takes) {
                                    if (!committedPuts.remove(take)) {
                                        pendingTakes.add(take);
                                    }
                                }
                            }
                        }
                    } else if (record.getRecordType() == TransactionEventRecord.Type.ROLLBACK.get()) {
                        if (uncommittedPuts.containsKey(record.getTransactionID())) {
                            uncommittedPuts.removeAll(record.getTransactionID());
                        } else {
                            uncommittedTakes.removeAll(record.getTransactionID());
                        }
                    }
                }
            }
        } catch (Exception e) {
            LOG.warn("Error while generating checkpoint " + "using fast generation logic", e);
            return false;
        } finally {
            TransactionIDOracle.setSeed(transactionIDSeed);
            WriteOrderOracle.setSeed(writeOrderIDSeed);
            for (LogFile.SequentialReader reader : logReaders) {
                reader.close();
            }
        }
        Set<ComparableFlumeEventPointer> sortedPuts = Sets.newTreeSet(committedPuts);
        int count = 0;
        for (ComparableFlumeEventPointer put : sortedPuts) {
            queue.addTail(put.pointer);
            count++;
        }
        LOG.info("Replayed {} events using fast replay logic.", count);
        return true;
    }

    private void writeCheckpoint() throws IOException {
        long checkpointLogOrderID = 0;
        List<LogFile.MetaDataWriter> metaDataWriters = Lists.newArrayList();
        for (File logFile : logFiles) {
            String name = logFile.getName();
            metaDataWriters.add(LogFileFactory.getMetaDataWriter(logFile,
                    Integer.parseInt(name.substring(name.lastIndexOf('-') + 1))));
        }
        try {
            if (queue.checkpoint(true)) {
                checkpointLogOrderID = queue.getLogWriteOrderID();
                for (LogFile.MetaDataWriter metaDataWriter : metaDataWriters) {
                    metaDataWriter.markCheckpoint(checkpointLogOrderID);
                }
            }
        } catch (Exception e) {
            LOG.warn("Error while generating checkpoint " + "using fast generation logic", e);
        } finally {
            for (LogFile.MetaDataWriter metaDataWriter : metaDataWriters) {
                metaDataWriter.close();
            }
        }
    }

    private final class ComparableFlumeEventPointer implements Comparable<ComparableFlumeEventPointer> {

        private final FlumeEventPointer pointer;
        private final long orderID;

        public ComparableFlumeEventPointer(FlumeEventPointer pointer, long orderID) {
            Preconditions.checkNotNull(pointer,
                    "FlumeEventPointer cannot be" + "null while creating a ComparableFlumeEventPointer");
            this.pointer = pointer;
            this.orderID = orderID;
        }

        @Override
        public int compareTo(ComparableFlumeEventPointer o) {
            if (orderID < o.orderID) {
                return -1;
            } else { //Unfortunately same log order id does not mean same event
                //for older logs.
                return 1;
            }
        }

        @Override
        public int hashCode() {
            return pointer.hashCode();
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null) {
                return false;
            }
            if (o.getClass() != this.getClass()) {
                return false;
            }
            return pointer.equals(((ComparableFlumeEventPointer) o).pointer);
        }
    }

    public static void main(String[] args) throws Exception {
        Options options = new Options();
        Option opt = new Option("c", true, "checkpoint directory");
        opt.setRequired(true);
        options.addOption(opt);
        opt = new Option("l", true, "comma-separated list of log directories");
        opt.setRequired(true);
        options.addOption(opt);
        options.addOption(opt);
        opt = new Option("t", true, "capacity of the channel");
        opt.setRequired(true);
        options.addOption(opt);
        CommandLineParser parser = new GnuParser();
        CommandLine cli = parser.parse(options, args);
        File checkpointDir = new File(cli.getOptionValue("c"));
        String[] logDirs = cli.getOptionValue("l").split(",");
        List<File> logFiles = Lists.newArrayList();
        for (String logDir : logDirs) {
            logFiles.addAll(LogUtils.getLogs(new File(logDir)));
        }
        int capacity = Integer.parseInt(cli.getOptionValue("t"));
        File checkpointFile = new File(checkpointDir, "checkpoint");
        if (checkpointFile.exists()) {
            LOG.error("Cannot execute fast replay",
                    new IllegalStateException("Checkpoint exists" + checkpointFile));
        } else {
            EventQueueBackingStore backingStore = EventQueueBackingStoreFactory.get(checkpointFile, capacity,
                    "channel");
            FlumeEventQueue queue = new FlumeEventQueue(backingStore, new File(checkpointDir, "inflighttakes"),
                    new File(checkpointDir, "inflightputs"));
            CheckpointRebuilder rebuilder = new CheckpointRebuilder(logFiles, queue);
            if (rebuilder.rebuild()) {
                rebuilder.writeCheckpoint();
            } else {
                LOG.error("Could not rebuild the checkpoint due to errors.");
            }
        }
    }
}