/** * $Id: MapedFile.java 1831 2013-05-16 01:39:51Z shijia.wxr $ */ package com.alibaba.rocketmq.store; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileChannel.MapMode; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.rocketmq.common.MixAll; import com.alibaba.rocketmq.common.UtilALl; /** * Pagecache�ļ����ʷ�װ * * @author vintage.wang@gmail.com shijia.wxr@taobao.com */ public class MapedFile extends ReferenceResource { private static final Logger log = LoggerFactory.getLogger(MixAll.StoreLoggerName); public static final int OS_PAGE_SIZE = 1024 * 4; // ��ǰJVM��ӳ��������ڴ��ܴ�С private static final AtomicLong TotalMapedVitualMemory = new AtomicLong(0); // ��ǰJVM��mmap������� private static final AtomicInteger TotalMapedFiles = new AtomicInteger(0); // ӳ����ļ��� private final String fileName; // ӳ�����ʼƫ���� private final long fileFromOffset; // ӳ����ļ���С������ private final int fileSize; // ӳ����ļ� private final File file; // ӳ���FileChannel���� private final FileChannel fileChannel; // ӳ����ڴ����position��Զ���� private final MappedByteBuffer mappedByteBuffer; // ��ǰд��ʲôλ�� private final AtomicInteger wrotePostion = new AtomicInteger(0); // Flush��ʲôλ�� private final AtomicInteger committedPosition = new AtomicInteger(0); // ���һ����Ϣ�洢ʱ�� private volatile long storeTimestamp = 0; private boolean firstCreateInQueue = false; public MapedFile(final String fileName, final int fileSize) throws IOException { this.fileName = fileName; this.fileSize = fileSize; this.file = new File(fileName); this.fileFromOffset = Long.parseLong(this.file.getName()); boolean ok = false; ensureDirOK(this.file.getParent()); try { this.fileChannel = new RandomAccessFile(this.file, "rw").getChannel(); this.mappedByteBuffer = this.fileChannel.map(MapMode.READ_WRITE, 0, fileSize); TotalMapedVitualMemory.addAndGet(fileSize); TotalMapedFiles.incrementAndGet(); ok = true; } catch (FileNotFoundException e) { log.error("create file channel " + this.fileName + " Failed. ", e); throw e; } catch (IOException e) { log.error("map file " + this.fileName + " Failed. ", e); throw e; } finally { if (!ok && this.fileChannel != null) { this.fileChannel.close(); } } } public static void ensureDirOK(final String dirName) { if (dirName != null) { File f = new File(dirName); if (!f.exists()) { boolean result = f.mkdirs(); log.info(dirName + " mkdir " + (result ? "OK" : "Failed")); } } } public long getLastModifiedTimestamp() { return this.file.lastModified(); } public String getFileName() { return fileName; } /** * �ļ���ʼƫ���� */ public long getFileFromOffset() { return this.fileFromOffset; } /** * ��ȡ�ļ���С */ public int getFileSize() { return fileSize; } public FileChannel getFileChannel() { return fileChannel; } public boolean isFull() { return this.fileSize == this.wrotePostion.get(); } /** * ��MapedBuffer׷����Ϣ<br> * * @param msg * Ҫ׷�ӵ���Ϣ * @param cb * ��������Ϣ�������л��������������MapedFile Offset�����Խ��ж�̬���л� * @return �Ƿ�ɹ���д��������� */ public AppendMessageResult appendMessage(final Object msg, final AppendMessageCallback cb) { assert msg != null; assert cb != null; int currentPos = this.wrotePostion.get(); // ��ʾ�п���ռ� if (currentPos < this.fileSize) { ByteBuffer byteBuffer = this.mappedByteBuffer.slice(); byteBuffer.position(currentPos); AppendMessageResult result = cb.doAppend(this.getFileFromOffset(), byteBuffer, this.fileSize - currentPos, msg); this.wrotePostion.addAndGet(result.getWroteBytes()); this.storeTimestamp = result.getStoreTimestamp(); return result; } // �ϲ�Ӧ��Ӧ�ñ�֤�����ߵ����� log.error("MapedFile.appendMessage return null, wrotePostion: " + currentPos + " fileSize: " + this.fileSize); return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR); } /** * ��洢��׷�����ݣ�һ����SLAVE�洢�ṹ��ʹ�� * * @return ����д���˶������� */ public boolean appendMessage(final byte[] data) { int currentPos = this.wrotePostion.get(); // ��ʾ�п���ռ� if ((currentPos + data.length) <= this.fileSize) { ByteBuffer byteBuffer = this.mappedByteBuffer.slice(); byteBuffer.position(currentPos); byteBuffer.put(data); this.wrotePostion.addAndGet(data.length); return true; } return false; } private boolean isAbleToFlush(final int flushLeastPages) { int flush = this.committedPosition.get(); int write = this.wrotePostion.get(); // �����ǰ�ļ��Ѿ�д����Ӧ������ˢ�� if (this.isFull()) { return true; } // ֻ��δˢ����������ָ��page��Ŀ��ˢ�� if (flushLeastPages > 0) { return ((write / OS_PAGE_SIZE) - (flush / OS_PAGE_SIZE)) >= flushLeastPages; } return write > flush; } /** * ��Ϣˢ�� * * @param flushLeastPages * ����ˢ����page * @return */ public int commit(final int flushLeastPages) { if (this.isAbleToFlush(flushLeastPages)) { if (this.hold()) { int value = this.wrotePostion.get(); this.mappedByteBuffer.force(); this.committedPosition.set(value); this.release(); } else { log.warn("in commit, hold failed, commit offset = " + this.committedPosition.get()); this.committedPosition.set(this.wrotePostion.get()); } } return this.getCommittedPosition(); } public SelectMapedBufferResult selectMapedBuffer(int pos, int size) { // ����Ϣ if ((pos + size) <= this.wrotePostion.get()) { // ��MapedBuffer�� if (this.hold()) { ByteBuffer byteBuffer = this.mappedByteBuffer.slice(); byteBuffer.position(pos); ByteBuffer byteBufferNew = byteBuffer.slice(); byteBufferNew.limit(size); return new SelectMapedBufferResult(this.fileFromOffset + pos, byteBufferNew, size, this); } else { log.warn("matched, but hold failed, request pos: " + pos + ", fileFromOffset: " + this.fileFromOffset); } } // ��������Ƿ� else { log.warn("selectMapedBuffer request pos invalid, request pos: " + pos + ", size: " + size + ", fileFromOffset: " + this.fileFromOffset); } // �Ƿ���������mmap��Դ�Ѿ����ͷ� return null; } /** * ���߼����� */ public SelectMapedBufferResult selectMapedBuffer(int pos) { if (pos < this.wrotePostion.get() && pos >= 0) { if (this.hold()) { ByteBuffer byteBuffer = this.mappedByteBuffer.slice(); byteBuffer.position(pos); int size = this.wrotePostion.get() - pos; ByteBuffer byteBufferNew = byteBuffer.slice(); byteBufferNew.limit(size); return new SelectMapedBufferResult(this.fileFromOffset + pos, byteBufferNew, size, this); } } // �Ƿ���������mmap��Դ�Ѿ����ͷ� return null; } public static void clean(final ByteBuffer buffer) { if (buffer == null || !buffer.isDirect() || buffer.capacity() == 0) return; invoke(invoke(viewed(buffer), "cleaner"), "clean"); } private static Object invoke(final Object target, final String methodName, final Class<?>... args) { return AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { try { Method method = method(target, methodName, args); method.setAccessible(true); return method.invoke(target); } catch (Exception e) { throw new IllegalStateException(e); } } }); } private static Method method(Object target, String methodName, Class<?>[] args) throws NoSuchMethodException { try { return target.getClass().getMethod(methodName, args); } catch (NoSuchMethodException e) { return target.getClass().getDeclaredMethod(methodName, args); } } private static ByteBuffer viewed(ByteBuffer buffer) { ByteBuffer viewedBuffer = (ByteBuffer) invoke(buffer, "viewedBuffer"); if (viewedBuffer == null) return buffer; else return viewed(viewedBuffer); } @Override public boolean cleanup(final long currentRef) { // ���û�б�shutdown���򲻿���unmap�ļ��������crash if (this.isAvailable()) { log.error("this file[REF:" + currentRef + "] " + this.fileName + " have not shutdown, stop unmaping."); return false; } // ����Ѿ�cleanup���ٴβ���������crash if (this.isCleanupOver()) { log.error("this file[REF:" + currentRef + "] " + this.fileName + " have cleanup, do not do it again."); // ���뷵��true return true; } clean(this.mappedByteBuffer); TotalMapedVitualMemory.addAndGet(this.fileSize * (-1)); TotalMapedFiles.decrementAndGet(); log.info("unmap file[REF:" + currentRef + "] " + this.fileName + " OK"); return true; } /** * ������Դ��destroy�����shutdown���̱߳�����ͬһ�� * * @return �Ƿ�destory�ɹ����ϲ������Ҫ��ʧ���������ʧ�ܺ������� */ public boolean destroy(final long intervalForcibly) { this.shutdown(intervalForcibly); if (this.isCleanupOver()) { try { this.fileChannel.close(); log.info("close file channel " + this.fileName + " OK"); long beginTime = System.currentTimeMillis(); boolean result = this.file.delete(); log.info("delete file[REF:" + this.getRefCount() + "] " + this.fileName + (result ? " OK, " : " Failed, ") + "W:" + this.getWrotePostion() + " M:" + this.getCommittedPosition() + ", " + UtilALl.computeEclipseTimeMilliseconds(beginTime)); } catch (Exception e) { log.warn("close file channel " + this.fileName + " Failed. ", e); } return true; } else { log.warn("destroy maped file[REF:" + this.getRefCount() + "] " + this.fileName + " Failed. cleanupOver: " + this.cleanupOver); } return false; } public int getWrotePostion() { return wrotePostion.get(); } public int getCommittedPosition() { return committedPosition.get(); } public void setWrotePostion(int pos) { this.wrotePostion.set(pos); } public void setCommittedPosition(int pos) { this.committedPosition.set(pos); } public static int getTotalmapedfiles() { return TotalMapedFiles.get(); } public static long getTotalMapedVitualMemory() { return TotalMapedVitualMemory.get(); } public MappedByteBuffer getMappedByteBuffer() { return mappedByteBuffer; } /** * ��������������ʱ���ã�����ȫ��ֻ������ʱ��reload��������ʱ���� */ public ByteBuffer sliceByteBuffer() { return this.mappedByteBuffer.slice(); } public long getStoreTimestamp() { return storeTimestamp; } public boolean isFirstCreateInQueue() { return firstCreateInQueue; } public void setFirstCreateInQueue(boolean firstCreateInQueue) { this.firstCreateInQueue = firstCreateInQueue; } }