Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
*
* 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.bookkeeper.bookie;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Sets;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.FastThreadLocal;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Pattern;
import org.apache.bookkeeper.bookie.storage.CompactionEntryLog;
import org.apache.bookkeeper.bookie.storage.EntryLogScanner;
import org.apache.bookkeeper.bookie.storage.EntryLogger;
import org.apache.bookkeeper.conf.ServerConfiguration;
import org.apache.bookkeeper.stats.NullStatsLogger;
import org.apache.bookkeeper.stats.StatsLogger;
import org.apache.bookkeeper.util.DiskChecker;
import org.apache.bookkeeper.util.HardLink;
import org.apache.bookkeeper.util.IOUtils;
import org.apache.bookkeeper.util.LedgerDirUtil;
import org.apache.bookkeeper.util.collections.ConcurrentLongLongHashMap;
import org.apache.bookkeeper.util.collections.ConcurrentLongLongHashMap.BiConsumerLong;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class manages the writing of the bookkeeper entries. All the new
* entries are written to a common log. The LedgerCache will have pointers
* into files created by this class with offsets into the files to find
* the actual ledger entry. The entry log files created by this class are
* identified by a long.
*/
public class DefaultEntryLogger implements EntryLogger {
private static final Logger LOG = LoggerFactory.getLogger(DefaultEntryLogger.class);
@VisibleForTesting
static final int UNINITIALIZED_LOG_ID = -0xDEAD;
static class BufferedLogChannel extends BufferedChannel {
private final long logId;
private final EntryLogMetadata entryLogMetadata;
private final File logFile;
private long ledgerIdAssigned = UNASSIGNED_LEDGERID;
public BufferedLogChannel(ByteBufAllocator allocator, FileChannel fc, int writeCapacity, int readCapacity,
long logId, File logFile, long unpersistedBytesBound) throws IOException {
super(allocator, fc, writeCapacity, readCapacity, unpersistedBytesBound);
this.logId = logId;
this.entryLogMetadata = new EntryLogMetadata(logId);
this.logFile = logFile;
}
public long getLogId() {
return logId;
}
public File getLogFile() {
return logFile;
}
public void registerWrittenEntry(long ledgerId, long entrySize) {
entryLogMetadata.addLedgerSize(ledgerId, entrySize);
}
public ConcurrentLongLongHashMap getLedgersMap() {
return entryLogMetadata.getLedgersMap();
}
public Long getLedgerIdAssigned() {
return ledgerIdAssigned;
}
public void setLedgerIdAssigned(Long ledgerId) {
this.ledgerIdAssigned = ledgerId;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(BufferedChannel.class)
.add("logId", logId)
.add("logFile", logFile)
.add("ledgerIdAssigned", ledgerIdAssigned)
.toString();
}
/**
* Append the ledger map at the end of the entry log.
* Updates the entry log file header with the offset and size of the map.
*/
void appendLedgersMap() throws IOException {
long ledgerMapOffset = this.position();
ConcurrentLongLongHashMap ledgersMap = this.getLedgersMap();
int numberOfLedgers = (int) ledgersMap.size();
// Write the ledgers map into several batches
final int maxMapSize = LEDGERS_MAP_HEADER_SIZE + LEDGERS_MAP_ENTRY_SIZE * LEDGERS_MAP_MAX_BATCH_SIZE;
final ByteBuf serializedMap = ByteBufAllocator.DEFAULT.buffer(maxMapSize);
try {
ledgersMap.forEach(new BiConsumerLong() {
int remainingLedgers = numberOfLedgers;
boolean startNewBatch = true;
int remainingInBatch = 0;
@Override
public void accept(long ledgerId, long size) {
if (startNewBatch) {
int batchSize = Math.min(remainingLedgers, LEDGERS_MAP_MAX_BATCH_SIZE);
int ledgerMapSize = LEDGERS_MAP_HEADER_SIZE + LEDGERS_MAP_ENTRY_SIZE * batchSize;
serializedMap.clear();
serializedMap.writeInt(ledgerMapSize - 4);
serializedMap.writeLong(INVALID_LID);
serializedMap.writeLong(LEDGERS_MAP_ENTRY_ID);
serializedMap.writeInt(batchSize);
startNewBatch = false;
remainingInBatch = batchSize;
}
// Dump the ledger in the current batch
serializedMap.writeLong(ledgerId);
serializedMap.writeLong(size);
--remainingLedgers;
if (--remainingInBatch == 0) {
// Close current batch
try {
write(serializedMap);
} catch (IOException e) {
throw new RuntimeException(e);
}
startNewBatch = true;
}
}
});
} catch (RuntimeException e) {
if (e.getCause() instanceof IOException) {
throw (IOException) e.getCause();
} else {
throw e;
}
} finally {
ReferenceCountUtil.release(serializedMap);
}
// Flush the ledger's map out before we write the header.
// Otherwise the header might point to something that is not fully
// written
super.flush();
// Update the headers with the map offset and count of ledgers
ByteBuffer mapInfo = ByteBuffer.allocate(8 + 4);
mapInfo.putLong(ledgerMapOffset);
mapInfo.putInt(numberOfLedgers);
mapInfo.flip();
this.fileChannel.write(mapInfo, LEDGERS_MAP_OFFSET_POSITION);
}
}
private final LedgerDirsManager ledgerDirsManager;
private final boolean entryLogPerLedgerEnabled;
final RecentEntryLogsStatus recentlyCreatedEntryLogsStatus;
/**
* locks for compaction log.
*/
private final Object compactionLogLock = new Object();
private volatile BufferedLogChannel compactionLogChannel;
final EntryLoggerAllocator entryLoggerAllocator;
private final EntryLogManager entryLogManager;
private final CopyOnWriteArrayList listeners = new CopyOnWriteArrayList();
private static final int HEADER_V0 = 0; // Old log file format (no ledgers map index)
private static final int HEADER_V1 = 1; // Introduced ledger map index
static final int HEADER_CURRENT_VERSION = HEADER_V1;
private static class Header {
final int version;
final long ledgersMapOffset;
final int ledgersCount;
Header(int version, long ledgersMapOffset, int ledgersCount) {
this.version = version;
this.ledgersMapOffset = ledgersMapOffset;
this.ledgersCount = ledgersCount;
}
}
/**
* The 1K block at the head of the entry logger file
* that contains the fingerprint and meta-data.
*
*
*/
static final int LOGFILE_HEADER_SIZE = 1024;
final ByteBuf logfileHeader = Unpooled.buffer(LOGFILE_HEADER_SIZE);
static final int HEADER_VERSION_POSITION = 4;
static final int LEDGERS_MAP_OFFSET_POSITION = HEADER_VERSION_POSITION + 4;
/**
* Ledgers map is composed of multiple parts that can be split into separated entries. Each of them is composed of:
*
*
* length: (4 bytes) [0-3]
* ledger id (-1): (8 bytes) [4 - 11]
* entry id: (8 bytes) [12-19]
* num ledgers stored in current metadata entry: (4 bytes) [20 - 23]
* ledger entries: sequence of (ledgerid, size) (8 + 8 bytes each) [24..]
*
*/
static final int LEDGERS_MAP_HEADER_SIZE = 4 + 8 + 8 + 4;
static final int LEDGERS_MAP_ENTRY_SIZE = 8 + 8;
// Break the ledgers map into multiple batches, each of which can contain up to 10K ledgers
static final int LEDGERS_MAP_MAX_BATCH_SIZE = 10000;
static final long INVALID_LID = -1L;
// EntryId used to mark an entry (belonging to INVALID_ID) as a component of the serialized ledgers map
static final long LEDGERS_MAP_ENTRY_ID = -2L;
static final int MIN_SANE_ENTRY_SIZE = 8 + 8;
static final long MB = 1024 * 1024;
private final int maxSaneEntrySize;
private final ByteBufAllocator allocator;
final ServerConfiguration conf;
/**
* Entry Log Listener.
*/
interface EntryLogListener {
/**
* Rotate a new entry log to write.
*/
void onRotateEntryLog();
}
public DefaultEntryLogger(ServerConfiguration conf) throws IOException {
this(conf, new LedgerDirsManager(conf, conf.getLedgerDirs(),
new DiskChecker(conf.getDiskUsageThreshold(), conf.getDiskUsageWarnThreshold())));
}
/**
* Create an EntryLogger that stores it's log files in the given directories.
*/
public DefaultEntryLogger(ServerConfiguration conf,
LedgerDirsManager ledgerDirsManager) throws IOException {
this(conf, ledgerDirsManager, null, NullStatsLogger.INSTANCE, PooledByteBufAllocator.DEFAULT);
}
public DefaultEntryLogger(ServerConfiguration conf,
LedgerDirsManager ledgerDirsManager, EntryLogListener listener, StatsLogger statsLogger,
ByteBufAllocator allocator) throws IOException {
//We reserve 500 bytes as overhead for the protocol. This is not 100% accurate
// but the protocol varies so an exact value is difficult to determine
this.maxSaneEntrySize = conf.getNettyMaxFrameSizeBytes() - 500;
this.allocator = allocator;
this.ledgerDirsManager = ledgerDirsManager;
this.conf = conf;
entryLogPerLedgerEnabled = conf.isEntryLogPerLedgerEnabled();
if (listener != null) {
addListener(listener);
}
// Initialize the entry log header buffer. This cannot be a static object
// since in our unit tests, we run multiple Bookies and thus EntryLoggers
// within the same JVM. All of these Bookie instances access this header
// so there can be race conditions when entry logs are rolled over and
// this header buffer is cleared before writing it into the new logChannel.
logfileHeader.writeBytes("BKLO".getBytes(UTF_8));
logfileHeader.writeInt(HEADER_CURRENT_VERSION);
logfileHeader.writerIndex(LOGFILE_HEADER_SIZE);
// Find the largest logId
long logId = INVALID_LID;
for (File dir : ledgerDirsManager.getAllLedgerDirs()) {
if (!dir.exists()) {
throw new FileNotFoundException(
"Entry log directory '" + dir + "' does not exist");
}
long lastLogId;
long lastLogFileFromFile = getLastLogIdFromFile(dir);
long lastLogIdInDir = getLastLogIdInDir(dir);
if (lastLogFileFromFile < lastLogIdInDir) {
LOG.info("The lastLogFileFromFile is {}, the lastLogIdInDir is {}, "
+ "use lastLogIdInDir as the lastLogId.", lastLogFileFromFile, lastLogIdInDir);
lastLogId = lastLogIdInDir;
} else {
lastLogId = lastLogFileFromFile;
}
if (lastLogId > logId) {
logId = lastLogId;
}
}
this.recentlyCreatedEntryLogsStatus = new RecentEntryLogsStatus(logId + 1);
this.entryLoggerAllocator = new EntryLoggerAllocator(conf, ledgerDirsManager, recentlyCreatedEntryLogsStatus,
logId, allocator);
if (entryLogPerLedgerEnabled) {
this.entryLogManager = new EntryLogManagerForEntryLogPerLedger(conf, ledgerDirsManager,
entryLoggerAllocator, listeners, recentlyCreatedEntryLogsStatus, statsLogger);
} else {
this.entryLogManager = new EntryLogManagerForSingleEntryLog(conf, ledgerDirsManager, entryLoggerAllocator,
listeners, recentlyCreatedEntryLogsStatus);
}
}
EntryLogManager getEntryLogManager() {
return entryLogManager;
}
void addListener(EntryLogListener listener) {
if (null != listener) {
listeners.add(listener);
}
}
/**
* If the log id of current writable channel is the same as entryLogId and the position
* we want to read might end up reading from a position in the write buffer of the
* buffered channel, route this read to the current logChannel. Else,
* read from the BufferedReadChannel that is provided.
* @param entryLogId
* @param channel
* @param buff remaining() on this bytebuffer tells us the last position that we
* expect to read.
* @param pos The starting position from where we want to read.
* @return
*/
private int readFromLogChannel(long entryLogId, BufferedReadChannel channel, ByteBuf buff, long pos)
throws IOException {
BufferedLogChannel bc = entryLogManager.getCurrentLogIfPresent(entryLogId);
if (null != bc) {
synchronized (bc) {
if (pos + buff.writableBytes() >= bc.getFileChannelPosition()) {
return bc.read(buff, pos);
}
}
}
return channel.read(buff, pos);
}
/**
* A thread-local variable that wraps a mapping of log ids to bufferedchannels
* These channels should be used only for reading. logChannel is the one
* that is used for writes.
*/
private final ThreadLocal