Netty零拷贝技术在RocketMQ中的实践

 

零拷贝技术

实现零拷贝有2种方式实现

1 mmap+write

系统调用函数会直接把内核缓冲区里的数据「映射」到用户空间,这样,操作系统内核与用户空间就不需要再进行任何的数据拷贝操作。

 

 

public static void mappedByteBufferTest() {
        try (RandomAccessFile randomAccessFile = new RandomAccessFile("netty/src/main/resources/1.txt", "rw");) {

            final FileChannel channel = randomAccessFile.getChannel();
            /**
             * 参数1 FileChannel.MapMode.READ_WRITE 读写模式
             * 参数2 0: 可以直接修改的起始位置
             * 参数3 5: 是映射到内存的大小,即将1.txt的多少个字节映射到内存
             * 实际类型:directByteBuffer
             */
            final MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);

            map.put(0, (byte) 'H');
            map.put(3, (byte) '9');

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

2 sendfile

 可以直接把内核缓冲区里的数据拷贝到 socket 缓冲区里,不再拷贝到用户态。

 

FileChannel类型

public abstract long transferTo(long position, long count, WritableByteChannel target)throws IOException;

  

 

netty零拷贝实现

CompositeByteBuf 

  • 指在 Java 之上(user space)允许 CompositeByteBuf 使用单个 ByteBuf 一样操作多个 ByteBuf 而不需要任何 copy。
  • 以及允许使用slice,切分单个ByteBuf为多个,而实际上操作的还是同一个ByteBuf,不需要cotpy。
package netty.zerocopy;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

import java.nio.charset.StandardCharsets;

/**
 * netty 零拷贝
 * @author huangyichun
 * @date 2020/12/9
 */
public class CompositeDemo {

    public static void main(String[] args) {
        ByteBuf buf1 = Unpooled.copiedBuffer("hello, world", StandardCharsets.UTF_8);

        ByteBuf buf2 = Unpooled.copiedBuffer("let's go", StandardCharsets.UTF_8);

        ByteBuf compositeBuf = Unpooled.wrappedBuffer(buf1, buf2);

        compositeBuf.setBytes(1, "my name".getBytes());

        System.out.println(buf1.toString(StandardCharsets.UTF_8));
        System.out.println(buf2.toString(StandardCharsets.UTF_8));
        System.out.println(compositeBuf.toString(StandardCharsets.UTF_8));
    }
}

  

 FileRegion 

  如果你所在的系统支持 zero copy,则可以使用 FileRegion 来写入 Channel,实际是就是调用上文Nio零拷贝中的transferTo方法进行传输。

 

 public void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        RandomAccessFile raf = null;

        long length = -1;
        try {
            raf = new RandomAccessFile(msg, "r");
            length = raf.length();
        } catch (Exception e) {
            ctx.writeAndFlush("ERR: " + e.getClass().getSimpleName() + ": " + e.getMessage() + '\n');
            return;
        } finally {
            if (length < 0 && raf != null) {
                raf.close();
            }
        }

        ctx.write("OK: " + raf.length() + '\n');
        if (ctx.pipeline().get(SslHandler.class) == null) {
            // SSL not enabled - can use zero-copy file transfer.
            ctx.write(new DefaultFileRegion(raf.getChannel(), 0, length));
        } else {
            // SSL enabled - cannot use zero-copy file transfer.
            ctx.write(new ChunkedFile(raf));
        }
        ctx.writeAndFlush("\n");
    }

 

RocketMQ使用FileRegion实现零拷贝

 在broker的拉取消息处理器PullMessageProcessor中,如果不使用堆内存,则使用Netty提供的零拷贝方案:

 

FileRegion fileRegion =
           new ManyMessageTransfer(response.encodeHeader(getMessageResult.getBufferTotalSize()), getMessageResult);
           
channel.writeAndFlush(fileRegion).addListener(
new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { getMessageResult.release(); if (!future.isSuccess()) { log.error("transfer many message by pagecache failed, {}", channel.remoteAddress(), future.cause()); } } });

创建 ManyMessageTransfer   extends AbstractReferenceCounted implements FileRegion ,具体看一下transferTo方法

    @Override
    public long transferTo(WritableByteChannel target, long position) throws IOException {
        if (this.byteBufferHeader.hasRemaining()) {
            transferred += target.write(this.byteBufferHeader);
            return transferred;
        } else {
            List<ByteBuffer> messageBufferList = this.getMessageResult.getMessageBufferList();
            for (ByteBuffer bb : messageBufferList) {
                if (bb.hasRemaining()) {
                    transferred += target.write(bb);
                    return transferred;
                }
            }
        }

        return 0;
    }

实际上,将多个缓冲区直接写入到socketchannel里面,避免了在内存中copy到一个缓冲区。

 

Ref:https://www.jianshu.com/p/3f9f56235d49

 

posted @ 2021-07-29 14:49  gaojy  阅读(277)  评论(0编辑  收藏  举报