網(wǎng)站建設(shè)服務(wù)內(nèi)容今天濟(jì)南剛剛發(fā)生的新聞
Netty零拷貝機(jī)制
- 一:用戶空間與內(nèi)核空間
- 二:傳統(tǒng)IO流程
- 三:零拷貝常見的實(shí)現(xiàn)方式
- 1. mmap + write
- 2. sendfile
- 四:Java中零拷貝
- 五:Netty 中如何實(shí)現(xiàn)零拷貝
- 1. CompositeByteBuf 實(shí)現(xiàn)零拷貝
- 2. wrap 實(shí)現(xiàn)零拷貝
- 3. slice 實(shí)現(xiàn)零拷貝
- 4. FileRegion實(shí)現(xiàn)零拷貝
一:用戶空間與內(nèi)核空間
操作系統(tǒng)的核心是內(nèi)核,它不同于普通應(yīng)用程序,所以為了保護(hù)內(nèi)核的存儲空間,操作系統(tǒng)將虛擬空間分為了兩個部分:內(nèi)核空間和用戶空間。
二:傳統(tǒng)IO流程
四次上下文切換,四次拷貝。
1、將磁盤文件,讀取到操作系統(tǒng)內(nèi)核緩沖區(qū);
2、將內(nèi)核緩沖區(qū)的數(shù)據(jù),拷貝到用戶空間的緩沖區(qū);
3、數(shù)據(jù)從用戶空間緩沖區(qū)拷貝到內(nèi)核的socket網(wǎng)絡(luò)發(fā)送緩沖區(qū);
4、數(shù)據(jù)從內(nèi)核的socket網(wǎng)絡(luò)發(fā)送緩沖區(qū)拷貝到網(wǎng)卡接口(硬件)的緩沖區(qū),由網(wǎng)卡進(jìn)行網(wǎng)絡(luò)傳輸。
三:零拷貝常見的實(shí)現(xiàn)方式
1. mmap + write
mmap通過內(nèi)存映射,將文件映射到內(nèi)核緩沖區(qū)。同時,用戶空間可以共享內(nèi)核空間的數(shù)據(jù)。這樣,在進(jìn)行網(wǎng)絡(luò)傳輸時,就可以減少內(nèi)核空間到用戶空間的拷貝次數(shù)。如下圖:
優(yōu)化后:三次拷貝 + 四次上下文切換。
2. sendfile
sendfile實(shí)現(xiàn)有兩個版本,我們這里只介紹真正實(shí)現(xiàn)零拷貝技術(shù)的后者,即通過SG-DMA技術(shù)實(shí)現(xiàn)數(shù)據(jù)拷貝。
Linux 2.1版本提供了sendFile函數(shù),其基本原理如下:
數(shù)據(jù)根本不經(jīng)過用戶態(tài),直接從內(nèi)核緩沖區(qū)進(jìn)入到Socket Buffer,同時,由于和用戶態(tài)完全無關(guān),就減少了一次上下文切換。
只需 2 次拷貝:
第一次使用 DMA 從磁盤文件拷貝到內(nèi)核緩沖區(qū);
第二次從內(nèi)核緩沖區(qū)將數(shù)據(jù)拷貝到網(wǎng)絡(luò)協(xié)議棧(網(wǎng)卡);
(內(nèi)核緩存區(qū)只會拷貝一些 offset 和 length 信息到SocketBuffer,基本無消耗)
優(yōu)化后:兩次上下文切換 + 兩次拷貝。
四:Java中零拷貝
通過Java的FileChannel.transferTo()方法實(shí)現(xiàn)零拷貝:
底層是調(diào)用Linux操作系統(tǒng)中的sendfile()實(shí)現(xiàn)的。
通過Java的FileChannel.map()方法實(shí)現(xiàn)零拷貝:
底層是調(diào)用Linux操作系統(tǒng)中的mmap()實(shí)現(xiàn)的。
再稍微講講 mmap 和 sendFile 的區(qū)別:
1、mmap 適合小數(shù)據(jù)量讀寫,sendFile 適合大文件傳輸。
2、mmap 需要 4 次上下文切換,3 次數(shù)據(jù)拷貝;sendFile 需要 3 次上下文切換,最少 2 次數(shù)據(jù)拷貝。
3、sendFile 可以利用 DMA 方式,減少 CPU 拷貝,mmap 則不能(必須從內(nèi)核拷貝到 Socket 緩沖區(qū))。
在這個選擇上:RocketMQ 在消費(fèi)消息時,使用了 mmap。Kafka 使用了 sendFile。
五:Netty 中如何實(shí)現(xiàn)零拷貝
netty中的零拷貝和操作系統(tǒng)中零拷貝有點(diǎn)不一樣,操作系統(tǒng)實(shí)現(xiàn)零拷貝主要是在內(nèi)核態(tài)的優(yōu)化,netty完全是在用戶狀態(tài)實(shí)現(xiàn)零拷貝。
1. CompositeByteBuf 實(shí)現(xiàn)零拷貝
通過將多個ByteBuf合并為一個邏輯上的ByteBuf,避免了各個ByteBuf之間的拷貝。
● 常規(guī)使用
ByteBuf allBuf = Unpooled.buffer(header.readableBytes() + body.readableBytes());
allBuf.writeBytes(header);
allBuf.writeBytes(body);
● 使用CompositeByteBuf
CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer();
compositeByteBuf.addComponents(true, header, body);
2. wrap 實(shí)現(xiàn)零拷貝
將byte[]數(shù)組、ByteBuf、ByteBuffer等包裝成一個Netty ByteBuf對象進(jìn)而避免了拷貝操作。
● 常規(guī)使用
byte[] bytes = ...
ByteBuf byteBuf = Unpooled.buffer();
byteBuf.writeBytes(bytes);
● 使用wrap
byte[] bytes = ...
ByteBuf byteBuf = Unpooled.wrappedBuffer(bytes);
3. slice 實(shí)現(xiàn)零拷貝
將ByteBuf 拆解成多個ByteBuf,但是共享同一存儲空間不同分區(qū),避免了內(nèi)存拷貝。
ByteBuf byteBuf = ...
ByteBuf header = byteBuf.slice(0, 5);
ByteBuf body = byteBuf.slice(5, 10);
4. FileRegion實(shí)現(xiàn)零拷貝
通過FileRegion包裝的FileChannel.tranferTo實(shí)現(xiàn)文件傳輸,可以直接將文件緩沖區(qū)的數(shù)據(jù)發(fā)送到目標(biāo)Channel,避免了傳統(tǒng)通過循環(huán)write方式導(dǎo)致的內(nèi)存拷貝問題。
● 傳統(tǒng)文件傳輸
public static void copyFile(String srcFile, String destFile) throws Exception {byte[] temp = new byte[1024];FileInputStream in = new FileInputStream(srcFile);FileOutputStream out = new FileOutputStream(destFile);int length;while ((length = in.read(temp)) != -1) {out.write(temp, 0, length);}in.close();out.close();
}
● file Region
public static void copyFileWithFileChannel(String srcFileName, String destFileName) throws Exception {RandomAccessFile srcFile = new RandomAccessFile(srcFileName, "r");FileChannel srcFileChannel = srcFile.getChannel();RandomAccessFile destFile = new RandomAccessFile(destFileName, "rw");FileChannel destFileChannel = destFile.getChannel();long position = 0;long count = srcFileChannel.size();srcFileChannel.transferTo(position, count, destFileChannel);
}