package com.ctriposs.sdb.table; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ctriposs.sdb.utils.FileUtil; import com.google.common.base.Preconditions; public abstract class AbstractMapTable implements Closeable, Comparable<AbstractMapTable> { static final Logger log = LoggerFactory.getLogger(AbstractMapTable.class); static int SIZE_OF_LONG_IN_BYTES = 8; static int SIZE_OF_INT_IN_BYTES = 4; public final static int INIT_INDEX_ITEMS_PER_TABLE = 128 * 1024; // length in bytes of an index item final static int INDEX_ITEM_LENGTH = 40; // size in bytes of initial index file final static int INIT_INDEX_FILE_SIZE = INDEX_ITEM_LENGTH * INIT_INDEX_ITEMS_PER_TABLE; // size in bytes of initial data file final static int INIT_DATA_FILE_SIZE = 128 * 1024 * 1024; final static int META_FILE_SIZE = 1 + SIZE_OF_INT_IN_BYTES + SIZE_OF_LONG_IN_BYTES; final static int TO_APPEND_INDEX_OFFSET = 1; final static int TO_APPEND_DATA_FILE_OFFSET = 1 + SIZE_OF_INT_IN_BYTES; public final static int NO_TIMEOUT = -1; protected RandomAccessFile metaRaf; protected RandomAccessFile indexRaf; protected RandomAccessFile dataRaf; protected FileChannel metaChannel; protected FileChannel dataChannel; protected FileChannel indexChannel; protected boolean usable = true; protected boolean closed = false; protected String dir; protected String fileName; protected String metaFile; protected String indexFile; protected String dataFile; protected AtomicInteger toAppendIndex; protected AtomicLong toAppendDataFileOffset; protected final Lock appendLock = new ReentrantLock(); // the level of the map store, start from 0, incremental. private int level; // the shard of the map store, start form 0, incremental. private short shard; // when this map store was created private long createdTime; public static final String DATA_FILE_SUFFIX = ".data"; public static final String INDEX_FILE_SUFFIX = ".index"; public static final String META_FILE_SUFFIX = ".meta"; public AbstractMapTable(String dir, int shard, int level, long createdTime) throws IOException { this.commonInit(dir, shard + "-" + level + "-" + createdTime); } public AbstractMapTable(String dir, int level, long createdTime) throws IOException { this(dir, 0, level, createdTime); } public AbstractMapTable(String dir, String fileName) throws IOException { this.commonInit(dir, fileName); } private void commonInit(String dir, String fileName) throws IOException { Preconditions.checkNotNull(dir); Preconditions.checkNotNull(fileName); this.fileName = fileName; this.initLevelAndCreatedTime(this.fileName); File dirFile = new File(dir); if (!dirFile.exists()) { dirFile.mkdirs(); } this.dir = dir; if (!this.dir.endsWith(File.separator)) { this.dir += File.separator; } this.metaFile = this.dir + this.fileName + META_FILE_SUFFIX; metaRaf = new RandomAccessFile(metaFile, "rw"); if (metaRaf.length() <= 0) metaRaf.setLength(META_FILE_SIZE); metaChannel = metaRaf.getChannel(); ByteBuffer byteBuf = ByteBuffer.allocate(1); metaChannel.read(byteBuf, 0); usable = (byteBuf.get(0) & 2) == 2; initIndexAndDataChannel(this.fileName); } private void initLevelAndCreatedTime(String fileName) { String[] parts = fileName.split("-"); shard = Short.parseShort(parts[0]); level = Integer.parseInt(parts[1]); createdTime = Long.parseLong(parts[2]); } private void initIndexAndDataChannel(String fileName) throws IOException { this.indexFile = this.dir + fileName + INDEX_FILE_SUFFIX; indexRaf = new RandomAccessFile(indexFile, "rw"); if (indexRaf.length() <= 0) indexRaf.setLength(INIT_INDEX_FILE_SIZE + INDEX_ITEM_LENGTH); // plus one padding indexChannel = indexRaf.getChannel(); this.dataFile = this.dir + fileName + DATA_FILE_SUFFIX; dataRaf = new RandomAccessFile(dataFile, "rw"); if (dataRaf.length() <= 0) dataRaf.setLength(INIT_DATA_FILE_SIZE); dataChannel = dataRaf.getChannel(); } public abstract IMapEntry getMapEntry(int index); public String getFileName() { return this.fileName; } public int getAppendedSize() { return toAppendIndex.get(); } public long getBackFileSize() throws IOException { ensureNotClosed(); return this.indexChannel.size() + this.dataChannel.size(); } public boolean isEmpty() { return toAppendIndex.get() == 0L; } public boolean isUsable() { return this.usable; } @Override public void close() throws IOException { if (this.metaChannel != null) { this.metaChannel.close(); this.metaChannel = null; } if (this.metaRaf != null) { this.metaRaf.close(); this.metaRaf = null; } if (this.indexChannel != null) { this.indexChannel.close(); this.indexChannel = null; } if (this.indexRaf != null) { this.indexRaf.close(); this.indexRaf = null; } if (this.dataChannel != null) { this.dataChannel.close(); this.dataChannel = null; } if (this.dataRaf != null) { this.dataRaf.close(); this.dataRaf = null; } this.closed = true; } @Override public int compareTo(AbstractMapTable mt) { if (level < mt.getLevel()) return -1; else if (level > mt.getLevel()) return 1; else { if (createdTime > mt.getCreatedTime()) return -1; else if (createdTime < mt.getCreatedTime()) return 1; else return 0; } } public int getLevel() { return level; } public long getCreatedTime() { return createdTime; } public short getShard() { return shard; } public void markUsable(boolean usable) throws IOException { ensureNotClosed(); ByteBuffer byteBuf = ByteBuffer.allocate(1); this.metaChannel.read(byteBuf, 0); byte status = byteBuf.get(0); if (usable) { status |= 2; // set 1 } else { status &= ~2; // set 0 } this.metaChannel.write(ByteBuffer.wrap(new byte[] {status}), 0); this.usable= usable; } public void delete() { Preconditions.checkArgument(closed, "Can't delete not closed map table!"); if (!FileUtil.deleteFile(this.metaFile)) { log.warn("fail to delete meta file " + this.metaFile + ", please delete it manully"); } if (!FileUtil.deleteFile(this.indexFile)) { log.warn("fail to delete index file " + this.indexFile + ", please delete it manully"); } if (!FileUtil.deleteFile(this.dataFile)) { log.warn("fail to delete data file " + this.dataFile + ", please delete it manully"); } } public abstract GetResult get(byte[] key) throws IOException; protected void ensureNotClosed() { if (closed) { throw new IllegalStateException("You can't work on a closed map table."); } } }