传统文件IO
普通的IO操作的一个弊端,必然涉及到两次数据拷贝操作,对磁盘读写性能是有影响的。
那么如果我们要将一些数据写入到磁盘文件里去呢?
那这个就是一样的过程了,必须先把数据写入到用户进程空间里去,然后从这里再进入内核空间,最后进入磁盘文件里去。
FileChannel、MMAP
fileChannel
mmap
如果 IO 非常频繁,数据却非常小,推荐使用 mmap,以避免 FileChannel 导致的切态问题。
ByteBuffer.slice
slice操作会重新生成一份position、limit、capacity,并共享同一份内存数据 addr。
position:当前位置指针
limit:当前已写位置指针
capacity:分配内存总大小指针
addr:内存地址指针
DefaultMmapFile中mappedByteBuffer的position是不使用,因为position不支持并发操作。所以需要slice一份指针信息但是内存是使用同一份的,这样做方便系统同时读写同一份内存。
DefaultMmapFile中使用wrotePosition来记录buffer的写入位置,同时WROTE_POSITION_UPDATER.addAndGet更新wrotePosition当前位置。
@Override
public boolean appendMessage(final byte[] data, final int offset, final int length) {
int currentPos = this.wrotePosition;
if ((currentPos + length) <= this.fileSize) {
ByteBuffer byteBuffer = this.mappedByteBuffer.slice();
byteBuffer.position(currentPos);
byteBuffer.put(data, offset, length);
WROTE_POSITION_UPDATER.addAndGet(this, length);
return true;
}
return false;
}
读取数据时,获取wrotePosition判断读取的位置是否大于写入位置,大于写入位置则返回null。
通过byteBuffer.position(pos)和byteBufferNew.limit(size)限制当前pos的数据消息。
@Override
public SelectMmapBufferResult selectMappedBuffer(int pos) {
int readPosition = getReadPosition();
if (pos < readPosition && pos >= 0) {
if (this.hold()) {
ByteBuffer byteBuffer = this.mappedByteBuffer.slice();
byteBuffer.position(pos);
int size = readPosition - pos;
ByteBuffer byteBufferNew = byteBuffer.slice();
byteBufferNew.limit(size);
return new SelectMmapBufferResult(this.fileFromOffset + pos, byteBufferNew, size, this);
}
}
return null;
}
DefaultMmapFile中使用flushedPosition来记录buffer的刷盘位置。
调用完mappedByteBuffer.force()后记录更新刷盘位置。
@Override
public int flush(final int flushLeastPages) {
if (this.isAbleToFlush(flushLeastPages)) {
if (this.hold()) {
int value = getReadPosition();
try {
this.mappedByteBuffer.force();
} catch (Throwable e) {
logger.error("Error occurred when force data to disk.", e);
}
FLUSHED_POSITION_UPDATER.set(this, value);
this.release();
} else {
logger.warn("in flush, hold failed, flush offset = " + this.flushedPosition);
FLUSHED_POSITION_UPDATER.set(this, getReadPosition());
}
}
return this.getFlushedPosition();
}