目录
一、 背景简介
二、 netty零拷贝
一、背景简介
在TCP网络数据传输过程中,数据包有可能被分割为独立的几个数据包进行发送,对于服务器接收端来说,单个的数据包是没有任何意义的,只有将这些数据包组合到一起,才能接收后解码然后提供给我们的业务层进行处理,而在数据报价进行整合的过程中,零拷贝做的工作就是避免或者减少内存区域中数据的复制次数,进而提高程序的响应性能。
二、 netty零拷贝
netty对于零拷贝有几种不同的形式,其本质上都是讲两个或者多个ByteBuf逻辑上组合到一起,物理上不一定是连续的,在操作数据时候, 不必再将一个ByteBuf拷贝到另一个ByteBuf中去. 因此少了内存的拷贝。而且netty的ByteBuf 支持 slice 操作, 可以将 ByteBuf 分解为多个 ByteBuf, 这些ByteBuf共享同一个内存区域,这样也就避免了内存的拷贝。
CompositeByteBuf 零拷贝:
CompositeByteBuf字面意思就是组合ByteBuf,他会把多个ByteBuf组合到一起,这些ByteBuf在逻辑上连续,实际物理地址不一定连续。
CompositeByteBuf中主要通过addComponent方法进行ByteBuf的合并。
进入到addComponent方法
public CompositeByteBuf addComponents(boolean increaseWriterIndex, ByteBuf... buffers) {
addComponents0(increaseWriterIndex, components.size(), buffers, 0, buffers.length);
consolidateIfNeeded();
return this;
}
核心逻辑就是addComponent0方法,consolidateIfNeeded()仅仅是判断合并组件数是否超过当前操作允许的最大值,如果超过最大值,就会进行扩容操作,如下:
private void consolidateIfNeeded() {
// 合并组件数是否超过当前操作允许的最大值。
final int numComponents = components.size();
if (numComponents > maxNumComponents) {
final int capacity = components.get(numComponents - 1).endOffset;
ByteBuf consolidated = allocBuffer(capacity);
// 我们没有使用foreach来避免创建迭代器。
for (int i = 0; i < numComponents; i ++) {
Component c = components.get(i);
ByteBuf b = c.buf;
consolidated.writeBytes(b);
c.freeIfNecessary();
}
Component c = new Component(consolidated);
c.endOffset = c.length;
components.clear();
components.add(c);
}
}
其扩容逻辑并不复杂。
继续来到addComponent0方法。
private int addComponents0(boolean increaseWriterIndex, int cIndex, ByteBuf[] buffers, int offset, int len) {
checkNotNull(buffers, "buffers");
int i = offset;
try {
// 校验Components index
checkComponentIndex(cIndex);
// 无需合并
while (i < len) {
// Increment i now to prepare for the next iteration and prevent a duplicate release (addComponent0
// will release if an exception occurs, and we also release in the finally block here).
ByteBuf b = buffers[i++];
if (b == null) {
break;
}
cIndex = addComponent0(increaseWriterIndex, cIndex, b) + 1;
int size = components.size();
if (cIndex > size) {
cIndex = size;
}
}
return cIndex;
} finally {
for (; i < len; ++i) {
ByteBuf b = buffers[i];
if (b != null) {
try {
b.release();
} catch (Throwable ignored) {
// 安静的释放ByteBuf
}
}
}
}
}
其核心逻辑就是通过while循环依次遍历每一个ByteBuf然后调用重载方法addComponent0进行添加每一个ByteBuf,最后将每一个ByteBuf进行内存释放。
接着进入重载方法addComponent0看看是如何添加每一个ByteBuf的。
private int addComponent0(boolean increaseWriterIndex, int cIndex, ByteBuf buffer) {
assert buffer != null;
boolean wasAdded = false;
try {
checkComponentIndex(cIndex);
int readableBytes = buffer.readableBytes();
// 无需合并-只需将组件添加到列表中.
@SuppressWarnings("deprecation")
Component c = new Component(buffer.order(ByteOrder.BIG_ENDIAN).slice());
if (cIndex == components.size()) {
wasAdded = components.add(c);
if (cIndex == 0) {
c.endOffset = readableBytes;
} else {
Component prev = components.get(cIndex - 1);
c.offset = prev.endOffset;
c.endOffset = c.offset + readableBytes;
}
} else {
components.add(cIndex, c);
wasAdded = true;
if (readableBytes != 0) {
updateComponentOffsets(cIndex);
}
}
if (increaseWriterIndex) {
writerIndex(writerIndex() + buffer.readableBytes());
}
return cIndex;
} finally {
if (!wasAdded) {
buffer.release();
}
}
}
其核心逻辑不难分析,主要就是添加组件、改变偏移量和end偏移量,如果开启了写索引递增,那么就将写索引加上每个ByteBuf的可读字节,最后如果添加组件失败,就将当前组件ByteBuf进行release释放。
总的来说CompositeByteBuf是通过内部封装的List<Component> components将ByteBuf进行合并,在合并的过程中我们可以手动设置是否递增写索引,进而从逻辑上可以操作多个ByteBuf而不必在物理地址上将其拷贝。
个人才疏学浅、信手涂鸦,netty框架更多模块解读相关源码持续更新中,感兴趣的朋友请移步至个人公众号,谢谢支持😜😜......
公众号:wenyixicodedog