您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關RoecketMQ存儲中如何實現映射文件預熱,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
1.為什么創建文件(commitLog)時要預熱?
2.為什么要寫入1G大小的假值(0)呢?
3.為什么要鎖定內存?
4.預熱流程是怎么樣的?
@1 AllocateMappedFileService#mmapOperation
// pre write mappedFile
if (mappedFile.getFileSize() >= this.messageStore.getMessageStoreConfig()
.getMapedFileSizeCommitLog() && this.messageStore.getMessageStoreConfig().isWarmMapedFileEnable()) {
//預熱
mappedFile.warmMappedFile(
this.messageStore.getMessageStoreConfig().getFlushDiskType(),
this.messageStore.getMessageStoreConfig()
.getFlushLeastPagesWhenWarmMapedFile()
);
}
@2 MappedFilewarmMappedFile
在文件預熱時為什么將1G假值(0)寫入文件呢?不寫這些值會怎么樣呢?
將預熱代碼改造下做個測試:分別映射空文件和將文件寫入1G假值0,觀察內存映射變化。
1.文件映射測試代碼
public static void main(String [] args) throws Exception {
File file = new File(args[0]);
FileChannel fileChannel = new RandomAccessFile(file, "rw").getChannel();
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 1024 * 1024 * 1024);
if(args.length >1 && args[1]!=null && Boolean.parseBoolean(args[1])){
for (int i = 0, j = 0; i < 1024 * 1024 * 1024; i += 1024 * 4, j++) {
mappedByteBuffer.put(i, (byte) 0);
}
}
final long address = ((DirectBuffer) (mappedByteBuffer)).address();
Pointer pointer = new Pointer(address);
{
int ret = LibC.INSTANCE.mlock(pointer, new NativeLong(1024 * 1024 * 102));
}
{
int ret = LibC.INSTANCE.madvise(pointer, new NativeLong(1024 * 1024 * 102), LibC.MADV_WILLNEED);
}
Thread.sleep(1000000000);
}
2.映射空文件
新建文件x.tmp,此文件為空,映射到內存會發生什么呢?
運行例子程序
java -jar melontst2.jar x.tmp
查看虛擬內存映射
cat /proc/xxx-pid/maps
7f4c48000000-7f4c88000000 rw-s 00000000 fd:00 395167 /home/baseuser/x.tmp
小結:
7f4c88000000-7f4c48000000 = 139966675943424 - 139965602201600
= 1024 * 1024 * 1024 = 1G。即:雖然是空文件,內存映射大小依然是1G大小。
3.映射1G文件
新建文件y.tmp, 寫入大小為1G字節0的數據,映射到內存會發生什么呢?
運行例子程序
java -jar melontst2.jar y.tmp true
查看虛擬內存映射
cat /proc/xxx-pid/maps
7f36e4000000-7f3724000000 rw-s 00000000 fd:00 395900 /home/baseuser/y.tmp
小結:內存映射大小計算
7f3724000000-7f36e4000000=139874803908608-139873730166784
=1073741824 = 1024 * 1024 * 1024 = 1G 內存分配了1G大小。
4.思考
既然空文件和寫入1G字節虛擬內存映射都是1G大小,寫入1G大小的意義呢?
使用mmap()內存分配時,只是建立了進程虛擬地址空間,并沒有分配虛擬內存對應的物理內存。當進程訪問這些沒有建立映射關系的虛擬內存時,處理器自動觸發一個缺頁異常,進而進入內核空間分配物理內存、更新進程緩存表,最后返回用戶空間,回復進程運行。
小結:寫入這些假值的意義在于實際分配物理內存,在消息寫入時防止缺頁異常。
5.內存映射簡圖
虛擬內存
計算機系統內存管理的一種技術。它使得應用程序認為它擁有連續的可用的內存(一個連續完整的地址空間),而實際上,它通常是被分隔成多個物理內存碎片,還有部分暫時存儲在外部磁盤存儲器上,在需要時進行數據交換
虛擬地址空間的內部又被分為內核空間和用戶空間兩部分,進程在用戶態時,只能訪問用戶空間內存;只有進入內核態后,才可以訪問內核空間內存
MMU
MMU是Memory Management Unit的縮寫,中文名是內存管理單元,它是中央處理器(CPU)中用來管理虛擬存儲器、物理存儲器的控制線路,同時也負責虛擬地址映射為物理地址
頁表
是虛擬內存系統用來存儲邏輯地址和物理地址之間映射的數據結構
內存映射mmap
將虛擬地址映射到物理地址
mmap
映射文件或設備到內存
void mmap(void start, size_t length, int prot, int flags, int fd, off_t offset);
The mmap() function asks to map length bytes starting at offset offset from the file (or other object) specified by the file descriptor fd into memory, preferably at address start. This latter address is a hint only, and is usually specified as 0. The actual place where the object is mapped is returned by mmap().
mlock
鎖定內存
int mlock(const void addr, size_t len);
mlock() locks pages in the address range starting at addr and continuing for len bytes. All pages that contain a part of the specified address range are guaranteed to be resident in RAM when the call returns successfully; the pages are guaranteed to stay in RAM until later unlocked.
madvise
提出建議關于使用內存
int madvise(void *start, size_t length, int advice);
The madvise() system call advises the kernel about how to handle paging input/output in the address range beginning at address start and with size length bytes. It allows an application to tell the kernel how it expects to use some mapped or shared memory areas, so that the kernel can choose appropriate read-ahead and caching techniques. This call does not influence the semantics of the application (except in the case ofMADV_DONTNEED), but may influence its performance. The kernel is free to ignore the advice。
MADV_WILLNEED模式(MappedFile預熱使用該模式)
MADV_WILLNEED:Expect access in the near future. (Hence, it might be a good idea to read some pages ahead.)
1.Broker配置warmMapedFileEnable為false,開啟預熱需要設置true。
2.寫入1G字節假值0是為了讓系統分配物理內存空間,如果沒有這些假值,系統不會實際分配物理內存,防止在寫入消息時發生缺頁異常。
3.mlock鎖定內存,防止其被交換到swap空間。
4.madvise建議操作系統如何使用內存,MADV_WILLNEED提前預熱,預讀一些頁面,提高性能。
5.文件預熱使得內存提前分配,并鎖定在內存中,在寫入消息時不必再進行內存分配。
1.文件初始化源代碼
private void init(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 {
//通過RandomAccessFile創讀寫文件通道
this.fileChannel = new RandomAccessFile(this.file, "rw").getChannel();
//將文件內容通過NIO的內存映射Buffer,將文件映射到內存
//將磁盤文件讀到內存中,每個文件大小為1G
this.mappedByteBuffer = this.fileChannel.map(MapMode.READ_WRITE, 0, fileSize);
TOTAL_MAPPED_VIRTUAL_MEMORY.addAndGet(fileSize);
TOTAL_MAPPED_FILES.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();
}
}
}
2.預熱源代碼
public void warmMappedFile(FlushDiskType type, int pages) {
long beginTime = System.currentTimeMillis();
ByteBuffer byteBuffer = this.mappedByteBuffer.slice();
int flush = 0; //記錄上一次刷盤的字節數
long time = System.currentTimeMillis();
for (int i = 0, j = 0; i < this.fileSize; i += MappedFile.OS_PAGE_SIZE, j++) {
byteBuffer.put(i, (byte) 0);
// force flush when flush disk type is sync
//當刷盤策略為同步刷盤時,執行強制刷盤
//每修改pages個分頁刷一次盤 內存頁的大小為4K
if (type == FlushDiskType.SYNC_FLUSH) {
if ((i / OS_PAGE_SIZE) - (flush / OS_PAGE_SIZE) >= pages) {
flush = i;
mappedByteBuffer.force();
}
}
// prevent gc
//Linux CPU調度策略基于時間片 Thread.sleep 當前線程主動放棄CPU資源,立即進入就緒狀態
//防止一直搶占CPU資源不釋放
if (j % 1000 == 0) {
log.info("j={}, costTime={}", j, System.currentTimeMillis() - time);
time = System.currentTimeMillis();
try {
Thread.sleep(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// force flush when prepare load finished
if (type == FlushDiskType.SYNC_FLUSH) {
log.info("mapped file warm-up done, force to disk, mappedFile={}, costTime={}",
this.getFileName(), System.currentTimeMillis() - beginTime);
mappedByteBuffer.force();
}
log.info("mapped file warm-up done. mappedFile={}, costTime={}", this.getFileName(),
System.currentTimeMillis() - beginTime);
this.mlock();
}
3.內存鎖定代碼
public void mlock() {
final long beginTime = System.currentTimeMillis();
final long address = ((DirectBuffer) (this.mappedByteBuffer)).address();
Pointer pointer = new Pointer(address);
{
//鎖死
int ret = LibC.INSTANCE.mlock(pointer, new NativeLong(this.fileSize));
log.info("mlock {} {} {} ret = {} time consuming = {}", address, this.fileName, this.fileSize, ret, System.currentTimeMillis() - beginTime);
}
{
int ret = LibC.INSTANCE.madvise(pointer, new NativeLong(this.fileSize), LibC.MADV_WILLNEED);
log.info("madvise {} {} {} ret = {} time consuming = {}", address, this.fileName, this.fileSize, ret, System.currentTimeMillis() - beginTime);
}
}
關于“RoecketMQ存儲中如何實現映射文件預熱”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。