溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

RoecketMQ存儲(chǔ)中如何實(shí)現(xiàn)映射文件預(yù)熱

發(fā)布時(shí)間:2021-11-18 09:38:09 來源:億速云 閱讀:151 作者:小新 欄目:大數(shù)據(jù)

這篇文章將為大家詳細(xì)講解有關(guān)RoecketMQ存儲(chǔ)中如何實(shí)現(xiàn)映射文件預(yù)熱,小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。

一、問題描述

1.為什么創(chuàng)建文件(commitLog)時(shí)要預(yù)熱?
2.為什么要寫入1G大小的假值(0)呢?
3.為什么要鎖定內(nèi)存?
4.預(yù)熱流程是怎么樣的?

二、調(diào)用鏈
@1 AllocateMappedFileService#mmapOperation
// pre write mappedFile
if (mappedFile.getFileSize() >= this.messageStore.getMessageStoreConfig()
.getMapedFileSizeCommitLog() && this.messageStore.getMessageStoreConfig().isWarmMapedFileEnable()) {
//預(yù)熱
mappedFile.warmMappedFile(
this.messageStore.getMessageStoreConfig().getFlushDiskType(),
this.messageStore.getMessageStoreConfig()
.getFlushLeastPagesWhenWarmMapedFile()
);
}
@2 MappedFilewarmMappedFile
三、流程圖

RoecketMQ存儲(chǔ)中如何實(shí)現(xiàn)映射文件預(yù)熱

四、代碼驗(yàn)證

在文件預(yù)熱時(shí)為什么將1G假值(0)寫入文件呢?不寫這些值會(huì)怎么樣呢?
將預(yù)熱代碼改造下做個(gè)測(cè)試:分別映射空文件和將文件寫入1G假值0,觀察內(nèi)存映射變化。

1.文件映射測(cè)試代碼

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,此文件為空,映射到內(nèi)存會(huì)發(fā)生什么呢?
運(yùn)行例子程序

java -jar melontst2.jar x.tmp

查看虛擬內(nèi)存映射

cat /proc/xxx-pid/maps
7f4c48000000-7f4c88000000 rw-s 00000000 fd:00 395167 /home/baseuser/x.tmp

小結(jié):
7f4c88000000-7f4c48000000 = 139966675943424 - 139965602201600
= 1024 * 1024 * 1024 = 1G。即:雖然是空文件,內(nèi)存映射大小依然是1G大小。


3.映射1G文件

新建文件y.tmp, 寫入大小為1G字節(jié)0的數(shù)據(jù),映射到內(nèi)存會(huì)發(fā)生什么呢?
運(yùn)行例子程序

java -jar melontst2.jar y.tmp true

查看虛擬內(nèi)存映射

cat /proc/xxx-pid/maps
7f36e4000000-7f3724000000 rw-s 00000000 fd:00 395900 /home/baseuser/y.tmp

小結(jié):內(nèi)存映射大小計(jì)算
7f3724000000-7f36e4000000=139874803908608-139873730166784
=1073741824 = 1024 * 1024 * 1024 = 1G 內(nèi)存分配了1G大小。


4.思考

既然空文件和寫入1G字節(jié)虛擬內(nèi)存映射都是1G大小,寫入1G大小的意義呢?
使用mmap()內(nèi)存分配時(shí),只是建立了進(jìn)程虛擬地址空間,并沒有分配虛擬內(nèi)存對(duì)應(yīng)的物理內(nèi)存。當(dāng)進(jìn)程訪問這些沒有建立映射關(guān)系的虛擬內(nèi)存時(shí),處理器自動(dòng)觸發(fā)一個(gè)缺頁(yè)異常,進(jìn)而進(jìn)入內(nèi)核空間分配物理內(nèi)存、更新進(jìn)程緩存表,最后返回用戶空間,回復(fù)進(jìn)程運(yùn)行。


小結(jié):寫入這些假值的意義在于實(shí)際分配物理內(nèi)存,在消息寫入時(shí)防止缺頁(yè)異常。

5.內(nèi)存映射簡(jiǎn)圖

RoecketMQ存儲(chǔ)中如何實(shí)現(xiàn)映射文件預(yù)熱

虛擬內(nèi)存
計(jì)算機(jī)系統(tǒng)內(nèi)存管理的一種技術(shù)。它使得應(yīng)用程序認(rèn)為它擁有連續(xù)的可用的內(nèi)存(一個(gè)連續(xù)完整的地址空間),而實(shí)際上,它通常是被分隔成多個(gè)物理內(nèi)存碎片,還有部分暫時(shí)存儲(chǔ)在外部磁盤存儲(chǔ)器上,在需要時(shí)進(jìn)行數(shù)據(jù)交換
虛擬地址空間的內(nèi)部又被分為內(nèi)核空間和用戶空間兩部分,進(jìn)程在用戶態(tài)時(shí),只能訪問用戶空間內(nèi)存;只有進(jìn)入內(nèi)核態(tài)后,才可以訪問內(nèi)核空間內(nèi)存

MMU
MMU是Memory Management Unit的縮寫,中文名是內(nèi)存管理單元,它是中央處理器(CPU)中用來管理虛擬存儲(chǔ)器、物理存儲(chǔ)器的控制線路,同時(shí)也負(fù)責(zé)虛擬地址映射為物理地址

頁(yè)表
是虛擬內(nèi)存系統(tǒng)用來存儲(chǔ)邏輯地址和物理地址之間映射的數(shù)據(jù)結(jié)構(gòu)

內(nèi)存映射mmap
將虛擬地址映射到物理地址

五、Native API解釋

mmap
映射文件或設(shè)備到內(nèi)存
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
鎖定內(nèi)存
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
提出建議關(guān)于使用內(nèi)存
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預(yù)熱使用該模式)
MADV_WILLNEED:Expect access in the near future. (Hence, it might be a good idea to read some pages ahead.)

六、總結(jié)

1.Broker配置warmMapedFileEnable為false,開啟預(yù)熱需要設(shè)置true。
2.寫入1G字節(jié)假值0是為了讓系統(tǒng)分配物理內(nèi)存空間,如果沒有這些假值,系統(tǒng)不會(huì)實(shí)際分配物理內(nèi)存,防止在寫入消息時(shí)發(fā)生缺頁(yè)異常。
3.mlock鎖定內(nèi)存,防止其被交換到swap空間。
4.madvise建議操作系統(tǒng)如何使用內(nèi)存,MADV_WILLNEED提前預(yù)熱,預(yù)讀一些頁(yè)面,提高性能。
5.文件預(yù)熱使得內(nèi)存提前分配,并鎖定在內(nèi)存中,在寫入消息時(shí)不必再進(jìn)行內(nèi)存分配。

七、附錄源代碼

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創(chuàng)讀寫文件通道
this.fileChannel = new RandomAccessFile(this.file, "rw").getChannel();
//將文件內(nèi)容通過NIO的內(nèi)存映射Buffer,將文件映射到內(nèi)存
//將磁盤文件讀到內(nèi)存中,每個(gè)文件大小為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.預(yù)熱源代碼

public void warmMappedFile(FlushDiskType type, int pages) {
long beginTime = System.currentTimeMillis();
ByteBuffer byteBuffer = this.mappedByteBuffer.slice();
int flush = 0; //記錄上一次刷盤的字節(jié)數(shù)
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
//當(dāng)刷盤策略為同步刷盤時(shí),執(zhí)行強(qiáng)制刷盤
//每修改pages個(gè)分頁(yè)刷一次盤 內(nèi)存頁(yè)的大小為4K
if (type == FlushDiskType.SYNC_FLUSH) {
if ((i / OS_PAGE_SIZE) - (flush / OS_PAGE_SIZE) >= pages) {
flush = i;
mappedByteBuffer.force();
}
}
// prevent gc
//Linux CPU調(diào)度策略基于時(shí)間片 Thread.sleep 當(dāng)前線程主動(dòng)放棄CPU資源,立即進(jìn)入就緒狀態(tài)
//防止一直搶占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.內(nèi)存鎖定代碼

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);
}
}

關(guān)于“RoecketMQ存儲(chǔ)中如何實(shí)現(xiàn)映射文件預(yù)熱”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,使各位可以學(xué)到更多知識(shí),如果覺得文章不錯(cuò),請(qǐng)把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI