All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.journalkeeper.persistence.local.journal.LocalStoreFile Maven / Gradle / Ivy

/**
 * Licensed 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 io.journalkeeper.persistence.local.journal; import io.journalkeeper.persistence.local.cache.BufferHolder; import io.journalkeeper.persistence.local.cache.MemoryCacheManager; import io.journalkeeper.utils.locks.CasLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sun.misc.Cleaner; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.lang.reflect.Method; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.channels.ClosedByInterruptException; import java.nio.channels.FileChannel; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.StampedLock; /** * 支持并发、带缓存页、顺序写入的文件 */ public class LocalStoreFile implements StoreFile, BufferHolder { private static final Logger logger = LoggerFactory.getLogger(LocalStoreFile.class); // 缓存页类型 // 只读: // MAPPED_BUFFER:mmap映射内存镜像文件; // 读写: // DIRECT_BUFFER: 数据先写入DirectBuffer,异步刷盘到文件,性能最好; private final static int MAPPED_BUFFER = 0, DIRECT_BUFFER = 1, NO_BUFFER = -1; // 文件全局位置 private final long filePosition; // 文件头长度 private final int headerSize; // 对应的File private final File file; // buffer读写锁: // 访问(包括读和写)buffer时加读锁; // 加载、释放buffer时加写锁; private final StampedLock bufferLock = new StampedLock(); // 文件锁,读写文件时加锁 private final CasLock fileLock = new CasLock(); // 缓存页 private ByteBuffer pageBuffer = null; private int bufferType = NO_BUFFER; private MemoryCacheManager bufferPool; private final int capacity; private long lastAccessTime = System.currentTimeMillis(); // 当前刷盘位置 private int flushPosition; // 当前写入位置 private int writePosition = 0; private long timestamp = -1L; private AtomicBoolean forced = new AtomicBoolean(false); private volatile boolean writeClosed = true; private FileChannel fileChannel; private RandomAccessFile raf; LocalStoreFile(long filePosition, File base, int headerSize, MemoryCacheManager bufferPool, int maxFileDataLength) { this.filePosition = filePosition; this.headerSize = headerSize; this.bufferPool = bufferPool; this.file = new File(base, String.valueOf(filePosition)); if (file.exists() && file.length() > headerSize) { this.writePosition = (int) (file.length() - headerSize); this.flushPosition = writePosition; } this.capacity = Math.max(maxFileDataLength, (int )(file.length() - headerSize)); } @Override public File file() { return file; } @Override public long position() { return filePosition; } private void loadRoUnsafe() throws IOException { if (null != pageBuffer) throw new IOException("Buffer already loaded!"); bufferPool.allocateMMap(this); ByteBuffer loadBuffer; try (RandomAccessFile raf = new RandomAccessFile(file, "r"); FileChannel fileChannel = raf.getChannel()) { loadBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, headerSize, file.length() - headerSize); pageBuffer = loadBuffer; bufferType = MAPPED_BUFFER; pageBuffer.clear(); forced.set(true); } catch (ClosedByInterruptException cie) { throw cie; } catch (Throwable t) { logger.warn("Exception: ", t); bufferPool.releaseMMap(this); pageBuffer = null; throw t; } } private void loadRwUnsafe() throws IOException { if (bufferType == DIRECT_BUFFER) { return; } else if (bufferType == MAPPED_BUFFER) { unloadUnsafe(); } ByteBuffer buffer = bufferPool.allocateDirect(capacity, this); loadDirectBuffer(buffer); writeClosed = false; } private void loadDirectBuffer(ByteBuffer buffer) throws IOException { boolean needLoadFileContent = file.exists() && file.length() > headerSize; boolean writeTimestamp = !file.exists(); // 打开文件描述符 raf = new RandomAccessFile(file, "rw"); fileChannel = raf.getChannel(); if (writeTimestamp) { // 第一次创建文件写入头部预留128字节中0位置开始的前8字节长度:文件创建时间戳 writeTimestamp(); } if (needLoadFileContent) { logger.warn("Reload file for write! size: {}, file: {}.", file.length(), file); fileChannel.position(headerSize); int length; do { length = fileChannel.read(buffer); } while (length > 0); buffer.clear(); } this.pageBuffer = buffer; bufferType = DIRECT_BUFFER; } public long timestamp() { if (timestamp == -1L) { // 文件存在初始化时间戳 readTimestamp(); } return timestamp; } private void readTimestamp() { ByteBuffer timeBuffer = ByteBuffer.allocate(8); try (RandomAccessFile raf = new RandomAccessFile(file, "r"); FileChannel fileChannel = raf.getChannel()) { fileChannel.position(0); fileChannel.read(timeBuffer); } catch (Exception e) { logger.warn("Exception: ", e); } finally { timestamp = timeBuffer.getLong(0); } } private void writeTimestamp() { ByteBuffer timeBuffer = ByteBuffer.allocate(8); long creationTime = System.currentTimeMillis(); timeBuffer.putLong(0, creationTime); try (RandomAccessFile raf = new RandomAccessFile(file, "rw"); FileChannel fileChannel = raf.getChannel()) { fileChannel.position(0); fileChannel.write(timeBuffer); } catch (Exception e) { logger.warn("Exception:", e); } finally { timestamp = creationTime; } } @Override public boolean unload() { long stamp = bufferLock.writeLock(); try { if (isClean()) { unloadUnsafe(); return true; } else { return false; } } finally { bufferLock.unlockWrite(stamp); } } @Override public void forceUnload() { long stamp = bufferLock.writeLock(); try { unloadUnsafe(); } finally { bufferLock.unlockWrite(stamp); } } @Override public boolean hasPage() { return this.bufferType != NO_BUFFER; } @Override public ByteBuffer read(int position, int length) throws IOException { touch(); long stamp = bufferLock.readLock(); try { while (!hasPage()) { long ws = bufferLock.tryConvertToWriteLock(stamp); if (ws != 0L) { // 升级成写锁成功 stamp = ws; loadRoUnsafe(); } else { bufferLock.unlockRead(stamp); stamp = bufferLock.writeLock(); } } long rs = bufferLock.tryConvertToReadLock(stamp); if (rs != 0L) { stamp = rs; } ByteBuffer byteBuffer = pageBuffer.asReadOnlyBuffer(); byteBuffer.position(position); byteBuffer.limit(writePosition); ByteBuffer dest = ByteBuffer.allocate(Math.min(length, byteBuffer.remaining())); if (length < byteBuffer.remaining()) { byteBuffer.limit(byteBuffer.position() + length); } dest.put(byteBuffer); dest.flip(); return dest; } finally { bufferLock.unlock(stamp); } } @Override public Long readLong(int position) throws IOException{ touch(); long stamp = bufferLock.readLock(); try { while (!hasPage()) { long ws = bufferLock.tryConvertToWriteLock(stamp); if (ws != 0L) { // 升级成写锁成功 stamp = ws; loadRoUnsafe(); } else { bufferLock.unlockRead(stamp); stamp = bufferLock.writeLock(); } } long rs = bufferLock.tryConvertToReadLock(stamp); if (rs != 0L) { stamp = rs; } return pageBuffer.getLong(position); } finally { bufferLock.unlock(stamp); } } // Not thread safe! private int appendToPageBuffer(ByteBuffer byteBuffer) { pageBuffer.position(writePosition); int writeLength = byteBuffer.remaining(); pageBuffer.put(byteBuffer); writePosition += writeLength; return writeLength; } // Not thread safe! private int appendToPageBuffer(List byteBuffers) { pageBuffer.position(writePosition); int writeLength = 0; for (ByteBuffer byteBuffer : byteBuffers) { writeLength += byteBuffer.remaining(); pageBuffer.put(byteBuffer); } writePosition += writeLength; return writeLength; } @Override public int append(ByteBuffer byteBuffer) throws IOException { touch(); long stamp = bufferLock.readLock(); try { while (bufferType != DIRECT_BUFFER) { long ws = bufferLock.tryConvertToWriteLock(stamp); if (ws != 0L) { // 升级成写锁成功 stamp = ws; loadRwUnsafe(); } else { bufferLock.unlockRead(stamp); stamp = bufferLock.writeLock(); } } long rs = bufferLock.tryConvertToReadLock(stamp); if (rs != 0L) { stamp = rs; } return appendToPageBuffer(byteBuffer); } finally { bufferLock.unlock(stamp); } } @Override public int append(List byteBuffers) throws IOException { touch(); long stamp = bufferLock.readLock(); try { while (bufferType != DIRECT_BUFFER) { long ws = bufferLock.tryConvertToWriteLock(stamp); if (ws != 0L) { // 升级成写锁成功 stamp = ws; loadRwUnsafe(); } else { bufferLock.unlockRead(stamp); stamp = bufferLock.writeLock(); } } long rs = bufferLock.tryConvertToReadLock(stamp); if (rs != 0L) { stamp = rs; } return appendToPageBuffer(byteBuffers); } finally { bufferLock.unlock(stamp); } } private void touch() { lastAccessTime = System.currentTimeMillis(); } /** * 刷盘 */ // Not thread safe! @Override public int flush() throws IOException { long stamp = bufferLock.readLock(); try { if (writePosition > flushPosition && fileLock.tryLock()) { try { if (!file.exists()) { // 第一次创建文件写入头部预留128字节中0位置开始的前8字节长度:文件创建时间戳 writeTimestamp(); } return flushPageBuffer(); } finally { fileLock.unlock(); } } return 0; } finally { bufferLock.unlockRead(stamp); } } private void ensureOpen() { if (fileChannel == null || !fileChannel.isOpen()) { throw new IllegalStateException( String.format( "File %s is not open!", file.getAbsolutePath() ) ); } } private int flushPageBuffer() throws IOException { ensureOpen(); int flushEnd = writePosition; ByteBuffer flushBuffer = pageBuffer.asReadOnlyBuffer(); flushBuffer.position(flushPosition); flushBuffer.limit(flushEnd); fileChannel.position(headerSize + flushPosition); int flushSize = flushEnd - flushPosition; while (flushBuffer.hasRemaining()) { fileChannel.write(flushBuffer); } flushPosition = flushEnd; return flushSize; } // Not thread safe! @Override public void rollback(int position) throws IOException { long stamp = bufferLock.writeLock(); try { if (position < writePosition) { writePosition = position; } if (position < flushPosition) { fileLock.waitAndLock(); try { flushPosition = position; if (fileChannel != null && fileChannel.isOpen()) { fileChannel.truncate(position + headerSize); } else { try (RandomAccessFile raf = new RandomAccessFile(file, "rw"); FileChannel fileChannel = raf.getChannel()) { fileChannel.truncate(position + headerSize); } } } finally { fileLock.unlock(); } } } finally { bufferLock.unlockWrite(stamp); } } @Override public boolean isClean() { return flushPosition >= writePosition; } @Override public int writePosition() { return writePosition; } @Override public int fileDataSize() { return Math.max((int) file.length() - headerSize, 0); } @Override public int flushPosition() { return flushPosition; } @Override public long lastAccessTime() { return lastAccessTime; } private void unloadUnsafe() { if (MAPPED_BUFFER == this.bufferType) { unloadMappedBuffer(); } else if (DIRECT_BUFFER == this.bufferType) { unloadDirectBuffer(); } try { closeFileChannel(); } catch (IOException e) { logger.warn("Close file {} exception: ", file.getAbsolutePath(), e); } writeClosed = true; } private void unloadDirectBuffer() { final ByteBuffer direct = pageBuffer; pageBuffer = null; this.bufferType = NO_BUFFER; if (null != direct) bufferPool.releaseDirect(direct, this); } private void unloadMappedBuffer() { try { final Buffer mapped = pageBuffer; pageBuffer = null; this.bufferType = NO_BUFFER; if (null != mapped) { Method getCleanerMethod; getCleanerMethod = mapped.getClass().getMethod("cleaner"); getCleanerMethod.setAccessible(true); Cleaner cleaner = (Cleaner) getCleanerMethod.invoke(mapped, new Object[0]); cleaner.clean(); } bufferPool.releaseMMap(this); } catch (Exception e) { logger.warn("Release direct buffer exception: ", e); } } @Override public int size() { return capacity; } @Override public boolean isFree() { return isClean(); } @Override public boolean evict() { return unload(); } @Override public void closeWrite() { writeClosed = true; } @Override public boolean writable() { return bufferType == DIRECT_BUFFER && !writeClosed; } private void closeFileChannel() throws IOException { force(); if (null != fileChannel) { fileChannel.close(); } if (null != raf) { raf.close(); } } @Override public void force() throws IOException { if(forced.compareAndSet(false, true)) { fileLock.waitAndLock(); ensureOpen(); try { fileChannel.force(true); } catch (Throwable t) { forced.set(false); throw t; } finally { fileLock.unlock(); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy