「我正在参加会员专属活动-源码共读第一期,点击参加」
前语
零仿制
是老生常谈的话题了, 不管是Kafka
仍是Netty
都用到了零仿制的常识, 本篇侧重讲解了什么是零仿制, 一起在Java
和Netty
平分别是怎样完成零仿制的
往期文章:
- Netty源码剖析(一) backlog 参数 – ()
- Netty服务端初始化详解 – ()
- Netty服务端发动流程剖析 – ()
- Netty之第一次 TCP 衔接时产生了什么 – ()
- Netty之服务发动且注册成功之后 – ()
- Netty之服务端channel的初始化 – ()
- Netty「源码阅览」之 EventLoop 简单介绍到源码剖析 – ()
- Netty「源码阅览」之怎样处理 Java 的 epoll 空轮询 bug – ()
什么是零仿制
零仿制是指计算机在执行IO操作的时分, CPU不需要将数据从一个存储区仿制到另一个存储区, 从而减少上下文切换以及 CPU 仿制的时间, 这是一种IO操作优化技能
零仿制不是没有仿制数据, 而是减少用户态, 内核态的切换次数 和 CPU仿制次数
, 目前完成零仿制的首要三种办法分别是:
- mmap + write
- sendfile
- 带有DMA搜集仿制功能的 sendfile
mmap
虚拟内存把内核空间和用户空间的虚拟地址映射到同一个物理地址, 从而减少数据仿制次数, mmap
技能便是利用了虚拟内存的这个特色, 它将内核中的读缓冲区与用户空间的缓冲区进行映射, 一切的IO操作都在内核中完结
sendfile
sendfile
是Linux 2.1
版别之后内核引进的一个体系调用函数
sendfile
表明在两个文件描述符之间传输数据, 他是在操作体系内核中完结的, 避免了数据从内核缓冲区和用户缓冲区之间的仿制操作, 因而能够用其来完成零仿制
在Linux 2.4
版别之后, 对sendfile
进行了晋级, 引进了SG-DMA
技能, 能够直接从缓冲区中将数据读取到网卡, 这样的话能够省去CPU仿制
Java 完成的零仿制
mmap
在Java NIO
有一个ByteBuffer
的子类MappedByteBuffer
, 这个类采用direct buffer
也便是内存映射的办法读写文件内容. 这种办法直接调用体系底层的缓存, 没有JVM
和体系之间的仿制操作, 首要用户操作大文件
sendfile
FileChannel
的transferTo()
办法或许transferFrom()
办法,底层便是sendfile()
体系调用函数。
完成了数据直接从内核的读缓冲区传输到套接字缓冲区, 避免了用户态与内核态之间的数据仿制
Kafka便是使用到它
Netty 的零仿制
Netty
的哦零仿制首要体现在以下几个方面
- slice
- duplicate
- CompositeByteBuf
- ….
咱们首要讲一下slice
, 其他的下次必定
log 工具类
import io.netty.buffer.ByteBuf;
import static io.netty.buffer.ByteBufUtil.appendPrettyHexDump;
import static io.netty.util.internal.StringUtil.NEWLINE;
public class ByteBufUtil {
// 打印
public static void log(ByteBuf buf){
final int length = buf.readableBytes();
int rows = length / 16 + (length % 15 == 0 ? 0 : 1) + 4;
StringBuilder str = new StringBuilder(rows * 80 * 2)
.append("read index:").append(buf.readerIndex())
.append(" write index: ").append(buf.writerIndex())
.append(" capacity:").append(buf.capacity())
.append(NEWLINE);
appendPrettyHexDump(str, buf);
System.out.println(str.toString());
}
}
slice
对原始的ByteBuf
进行切片成多个ByteBuf
, 切片后的ByteBuf
并没有产生内存仿制, 仍是使用原始的ByteBuf
内存, 可是切片后的ByteBuf
各自有独立的read, write
指针
留意:
slice
不允许更改切片的容量, 切片时设置的长度是多少便是多少, 不允许扩容- 当咱们开释原始
ByteBuf
内存之后, 切片后的ByteBuf
就不能再访问了
测试:
- 首要创建一个
ByteBuf
, 然后对其进行切片 - 更改某一个切片查看原始
ByteBuf
是否更改 - 原始数据跟着更改了阐明内存地址没有产生改动
测试类
public static void main(String[] args) {
// 创建 ByteBuf
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(10);
// 向 byteBuf 缓冲区写入数据
StringBuilder str = new StringBuilder();
for (int i = 0; i < 5; i++) {
str.append("nx");
}
byteBuf.writeBytes(str.toString().getBytes());
// 打印当时 byteBug
ByteBufUtil.log(byteBuf);
// 切片的过程中并没有产生数据仿制
final ByteBuf slice = byteBuf.slice(0, 5);
final ByteBuf slice1 = byteBuf.slice(5, 5);
// 打印第一个切片
ByteBufUtil.log(slice);
// 打印第二个切片
ByteBufUtil.log(byteBuf);
slice.setByte(0, 'a');
System.out.println("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
// 打印第一个切片
ByteBufUtil.log(slice);
// 打印原始数组
ByteBufUtil.log(byteBuf);
}
打印成果如下
本文内容到此结束了
如有收成欢迎点赞收藏关注✔️,您的鼓舞是我最大的动力。
如有过错❌疑问欢迎各位大佬指出。
我是 宁轩 , 咱们下次再会