package com.github.eddyzhou.mcache; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.util.logging.Logger; /** * Mmap : Memory Map. */ public class Mmap { private static Logger log = Logger.getLogger(Mmap.class.getName()); private static final long MAX_FILE_SIZE = 0x7FFFFFFF; // 单个数据文件最大支持2G private MmapFile indexFile; //索引文件 private MemIndexMap indexMap; //内存索引 private MmapFile[] dataFiles; //数据文件,多个 private ByteBuffer[] dataBuffers; //数据内存缓冲区 private int dataNumOfOneFile; //每个文件的数据量 private int dataFileNum; //数据文件数量, 其中dataFiles数组的大小就是这个值 private int dataSize; //数据大小,每条记录的长度 private String statFile; private long statLastRecordTime; private long statGetCount; private long statPutCount; private long statUseMsec; private int statMaxDatasize; /** * * @param fileName fileName不要加后缀,会自动增加idx和dat后缀 * @param dataNum * @param dataSize */ public Mmap(String fileName, int dataNum, int dataSize) throws MmapException, IOException { statFile = fileName; //根据dataNum计算hashNum. int hashNum = MmapUtils.getlargerPrime(dataNum * 2); int conflictNum = Math.abs(dataNum / 2); this.dataSize = dataSize + 16; // indexFile int indexSize = MemIndexMap.calSize(hashNum, conflictNum, dataNum); File indexFile = new File(fileName + ".idx"); boolean needInit = !indexFile.exists(); this.indexFile = new MmapFile(indexFile, indexSize); ByteBuffer bb = this.indexFile.getBuffer(); this.indexMap = new MemIndexMap(bb, indexSize, hashNum, conflictNum, dataNum, this.dataSize, needInit); // dataFile long totalSize = 1L * (dataNum + 1) * this.dataSize; long size = 0; long lastFileSize = 0; //总大小/每条记录的长度=这个文件有多少条记录. 那么dataSize是固定的:即每条记录的长度是一样的 this.dataNumOfOneFile = (int) (MAX_FILE_SIZE) / this.dataSize; this.dataFileNum = 0; for (;;) { if (totalSize <= size) { break; } long left = totalSize - size; if (left > (this.dataNumOfOneFile * this.dataSize)) { left = this.dataNumOfOneFile * this.dataSize; } else { lastFileSize = left; } size += left; this.dataFileNum++; } log.info("mmap[" + fileName + "] start: " + indexMap.toString()); //磁盘文件 dataFiles = new MmapFile[dataFileNum]; //内存缓冲块 dataBuffers = new ByteBuffer[dataFileNum]; for (int i = 0; i < dataFileNum; i++) { //最后一个文件 if (i == dataFileNum - 1) { dataFiles[i] = new MmapFile(new File(fileName + ".dat" + i), (int) lastFileSize); } else { //除了最后一个文件,其他每个文件的大小都是固定的. dataFiles[i] = new MmapFile(new File(fileName + ".dat" + i), this.dataNumOfOneFile * this.dataSize); } dataBuffers[i] = dataFiles[i].getBuffer(); } } public boolean isEmpty() { return indexMap.isEmpty(); } public boolean isFull() { return indexMap.isFull(); } public int getUsedNum() { return indexMap.getUsedNum(); } public int getIdleNum() { return indexMap.getIdleNum(); } public int size() { return indexMap.size(); } public boolean contains(long key) { return indexMap.getIndex(key) > 0; } public byte[] get(long key) { ByteBuffer bb = getByteBuffer(key); if (bb == null) return null; byte[] bytes = new byte[bb.capacity()]; bb.slice().get(bytes); return bytes; } public ByteBuffer getByteBuffer(long key) { ++statGetCount; long startTime = System.currentTimeMillis(); doStat(startTime); int pos = indexMap.getIndex(key); if (pos == 0) { long endTime = System.currentTimeMillis(); statUseMsec += (endTime - startTime); return null; } ByteBuffer tmpBuffer = dataBuffers[pos / this.dataNumOfOneFile].duplicate(); tmpBuffer.position((pos % this.dataNumOfOneFile) * dataSize); tmpBuffer.limit((pos % this.dataNumOfOneFile) * dataSize + 12); long _key = tmpBuffer.slice().asLongBuffer().get(0); assert (_key == key); int len = tmpBuffer.slice().asIntBuffer().get(2); assert (len + 12 <= dataSize); tmpBuffer = dataBuffers[pos / this.dataNumOfOneFile].duplicate(); tmpBuffer.position((pos % this.dataNumOfOneFile) * dataSize + 12); tmpBuffer.limit((pos % this.dataNumOfOneFile) * dataSize + 12 + len); long endTime = System.currentTimeMillis(); statUseMsec += (endTime - startTime); return tmpBuffer.slice(); } // 存在则覆盖;不存在则新增 public void put(long key, byte[] bytes) throws MmapException { ++statPutCount; if (bytes.length > statMaxDatasize) statMaxDatasize = bytes.length; long startTime = System.currentTimeMillis(); doStat(startTime); if (bytes.length + 16 > dataSize) { throw new MmapException("Mmap put failed: data too big"); } int pos = indexMap.getIndex(key); //存在:覆盖. 能根据key找到索引位置,说明存在 if (pos > 0) { ByteBuffer tmpBuffer = dataBuffers[(pos / this.dataNumOfOneFile)].duplicate(); tmpBuffer.position((pos % this.dataNumOfOneFile) * dataSize); tmpBuffer.limit((pos % this.dataNumOfOneFile) * dataSize + 12); long _key = tmpBuffer.slice().asLongBuffer().get(0); assert (_key == key); // write len // key-长度-data-时间戳,(key-长度-时间戳)部分共占16个字节 int writeSize = ((dataSize - 16) > bytes.length ? bytes.length : (dataSize - 16)); tmpBuffer.asIntBuffer().put(2, writeSize); // write data tmpBuffer = dataBuffers[(pos / this.dataNumOfOneFile)].duplicate(); tmpBuffer.position((pos % this.dataNumOfOneFile) * dataSize + 12); tmpBuffer.limit(((pos % this.dataNumOfOneFile) + 1) * dataSize); tmpBuffer.slice().put(bytes, 0, writeSize); // 时间戳 tmpBuffer.position(((pos % this.dataNumOfOneFile) + 1) * dataSize - 4); tmpBuffer.slice().asIntBuffer().put(0, (int) (startTime / 1000)); } else { //不存在:新增. int _pos = indexMap.insertData(); ByteBuffer tmpBuffer = dataBuffers[_pos / this.dataNumOfOneFile].duplicate(); tmpBuffer.position((_pos % this.dataNumOfOneFile) * dataSize); tmpBuffer.limit((_pos % this.dataNumOfOneFile) * dataSize + 12); tmpBuffer.slice().asLongBuffer().put(0, key); int writeSize = ((dataSize - 16) > bytes.length ? bytes.length : (dataSize - 16)); // write len tmpBuffer.slice().asIntBuffer().put(2, writeSize); // write data tmpBuffer = dataBuffers[_pos / this.dataNumOfOneFile].duplicate(); tmpBuffer.position((_pos % this.dataNumOfOneFile) * dataSize + 12); tmpBuffer.limit(((_pos % this.dataNumOfOneFile) + 1) * dataSize); tmpBuffer.slice().put(bytes, 0, writeSize); tmpBuffer.limit(((_pos % this.dataNumOfOneFile) + 1) * dataSize - 4); tmpBuffer.slice().asIntBuffer().put(0, (int) startTime / 1000); try { indexMap.insertIndex(key, _pos); } catch (MmapException e) { // for reuse indexMap.freeData(_pos); tmpBuffer = dataBuffers[_pos / this.dataNumOfOneFile].duplicate(); tmpBuffer.position((_pos % this.dataNumOfOneFile) * dataSize); tmpBuffer.limit((_pos % this.dataNumOfOneFile) * dataSize + 12); tmpBuffer.slice().asLongBuffer().put(0, 0); tmpBuffer.slice().asIntBuffer().put(2, 0); } } long endTime = System.currentTimeMillis(); statUseMsec += (endTime - startTime); } public void free(long key) throws MmapException { ++statPutCount; long startTime = System.currentTimeMillis(); doStat(startTime); int pos = indexMap.getIndex(key); if (pos > 0) { ByteBuffer tmpBuffer = dataBuffers[pos / this.dataNumOfOneFile].duplicate(); tmpBuffer.position((pos % this.dataNumOfOneFile) * dataSize); tmpBuffer.limit((pos % this.dataNumOfOneFile) * dataSize + 12); tmpBuffer.slice().asLongBuffer().put(0, 0); tmpBuffer.slice().asIntBuffer().put(2, 0); indexMap.freeData(pos); indexMap.freeIndex(key); } long endTime = System.currentTimeMillis(); statUseMsec += (endTime - startTime); } @Override public String toString() { return indexMap.toString(); } private void doStat(long time) { if (time - statLastRecordTime > 1000 * 60 * 60) { statLastRecordTime = time; long avg = statUseMsec == 0 ? 0 : ((statGetCount + statPutCount) * 1000 / statUseMsec); long useNumRate = (getUsedNum() * 100) / size(); long dataSizeRate = (statMaxDatasize * 100) / dataSize; log.info("[" + statFile + "] stat:" + indexMap.toString() + " , [getCount=" + statGetCount + ", putCount=" + statPutCount + ", usedMsec=" + statUseMsec + ", avg=" + avg + ", maxDatasize=" + statMaxDatasize + ", useNumRate=" + useNumRate + ", dataSizeRate=" + dataSizeRate + "]"); if (useNumRate > 90) { log.warning("[" + statFile + "] warning: useNumRate=" + useNumRate); } if (dataSizeRate > 90) { log.warning("[" + statFile + "] warning: dataSizeRate=" + dataSizeRate); } } } }