![JAR search and dependency download from the Maven repository](/logo.png)
com.github.ltsopensource.kv.txlog.StoreTxLog Maven / Gradle / Ivy
package com.github.ltsopensource.kv.txlog;
import com.github.ltsopensource.core.commons.file.FileUtils;
import com.github.ltsopensource.core.logger.Logger;
import com.github.ltsopensource.kv.*;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Timer;
/**
* 记录格式
* 1.MAGIC_NUMBER 1 byte
* 2.timestamp 8 byte
* 3.logEntrySize 4 byte
* 4.logEntry
*
* 非线程安全
*
* @author Robert HG ([email protected]) on 12/13/15.
*/
public class StoreTxLog implements Closeable {
private static final Logger LOGGER = DB.LOGGER;
private StoreTxLog next;
private FileChannel fileChannel;
private StoreConfig storeConfig;
private final ByteBuffer entryBuffer;
private StoreTxLogFileHeader fileHeader;
private static final int ENTRY_HEAD_LENGTH = 1 + 4;
private static final byte magic = (byte) 0xA2;
public static final String LOG_FILE_SUFFIX = ".log";
private long lastCheckPointLength = 0;
private Timer syncTimer;
private FutureTimerTask syncTimerTask;
private FutureTimerTask.Callable syncCallable;
private long fileLength;
public StoreTxLog(StoreConfig storeConfig, File file, boolean readonly,
boolean isNewFile, long firstRecordId) throws IOException {
this.storeConfig = storeConfig;
this.entryBuffer = ByteBuffer.allocate(storeConfig.getMaxxLogEntryLength() + 1 + 4);
this.fileHeader = new StoreTxLogFileHeader();
if (!readonly) {
syncTimer = new Timer("ltsdb-dblog-sync-timer", true);
syncCallable = new FutureTimerTask.Callable() {
@Override
public void call() throws Exception {
checkPoint();
}
};
}
if (isNewFile && file.exists()) {
throw new IOException(file + " exists already");
} else {
FileUtils.createFileIfNotExist(file);
}
fileChannel = FileUtils.newFileChannel(file, "rw");
if (isNewFile) {
// 新文件长度为头部长度
fileLength = fileHeader.getLength();
fileHeader.setFirstRecordId(firstRecordId);
fileHeader.write(fileChannel);
} else {
fileHeader.read(fileChannel);
fileLength = fileChannel.size();
lastCheckPointLength = fileLength;
}
}
/**
* 新添加一条记录
*
* @return 返回记录ID
*/
public StoreTxLogPosition append(byte[] entry) throws IOException {
int length = entry.length;
if (length > storeConfig.getMaxxLogEntryLength()) {
throw new DBException("Value size can not great than " + storeConfig.getMaxxLogEntryLength());
}
// 检查当前文件容量是否足够
if (fileLength + length + ENTRY_HEAD_LENGTH > storeConfig.getTxLogFileSize()) {
throw new CapacityNotEnoughException();
}
StoreTxLogPosition result = new StoreTxLogPosition();
result.setRecordId(getNextRecordId());
boolean ok = false;
try {
entryBuffer.clear();
entryBuffer.put(magic); // 1 byte
entryBuffer.putInt(length); // 4 byte
entryBuffer.put(entry); // entry.length
entryBuffer.flip();
fileChannel.position(fileLength);
fileChannel.write(entryBuffer);
int entryLength = (ENTRY_HEAD_LENGTH + length);
fileLength += entryLength;
ok = true;
} finally {
if (ok) {
if (syncTimerTask == null || syncTimerTask.isDone()) {
syncTimerTask = new FutureTimerTask("ltsdb-dblog-sync-timertask", syncCallable);
syncTimer.schedule(syncTimerTask, storeConfig.getDbLogFlushInterval());
}
}
}
return result;
}
public byte[] readEntry(long position) throws IOException {
fileChannel.position(position);
entryBuffer.clear();
fileChannel.read(entryBuffer);
entryBuffer.position(0);
byte readMagic = entryBuffer.get();
if (readMagic != magic) {
throw new IOException("Invalid entry type magic number 0x" + Integer.toHexString(readMagic & 0xFFFF));
}
int length = entryBuffer.getInt();
byte[] entry = new byte[length];
entryBuffer.get(entry);
return entry;
}
public long nextEntryPosition(long position, long entryLength) {
return position + entryLength + 1 + 4;
}
public long getFileLength() {
return fileLength;
}
public long getNextRecordId() {
return fileHeader.getFirstRecordId() + fileLength;
}
public long getFirstRecordId() {
return fileHeader.getFirstRecordId();
}
private void checkPoint() throws IOException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("checkpoint start");
}
// 先将内容都强制刷到磁盘,因为后面会写头部汇总信息
if (fileLength != lastCheckPointLength) {
fileChannel.force(true);
lastCheckPointLength = fileLength;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("checkpoint end: fileLength=" + fileLength);
}
}
public void setNext(StoreTxLog next) {
this.next = next;
}
public StoreTxLog next() {
return this.next;
}
public int getHeaderLength() {
return fileHeader.getLength();
}
@Override
public void close() throws IOException {
checkPoint();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy