返回顶部
首页 > 资讯 > 前端开发 > JavaScript >怎么理解Netty、Kafka中的零拷贝技术
  • 339
分享到

怎么理解Netty、Kafka中的零拷贝技术

2024-04-02 19:04:59 339人浏览 八月长安
摘要

本篇内容介绍了“怎么理解Netty、kafka中的零拷贝技术”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

本篇内容介绍了“怎么理解Nettykafka中的零拷贝技术”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

缓冲区

缓冲区是所有 I/O 的基础,I/O 讲的无非就是把数据移进或移出缓冲区;进程执行 I/O  操作,就是向操作系统发出请求,让它要么把缓冲区的数据排干(写),要么填充缓冲区(读)。

下面看一个 Java 进程发起 Read 请求加载数据大致的流程图:

怎么理解Netty、Kafka中的零拷贝技术

进程发起 Read 请求之后,内核接收到 Read 请求之后,会先检查内核空间中是否已经存在进程所需要的数据,如果已经存在,则直接把数据 Copy  给进程的缓冲区。

如果没有内核随即向磁盘控制器发出命令,要求从磁盘读取数据,磁盘控制器把数据直接写入内核 Read 缓冲区,这一步通过 DMA 完成。

接下来就是内核将数据 Copy 到进程的缓冲区;如果进程发起 Write 请求,同样需要把用户缓冲区里面的数据 Copy 到内核的 Socket  缓冲区里面,然后再通过 DMA 把数据 Copy 到网卡中,发送出去。

你可能觉得这样挺浪费空间的,每次都需要把内核空间的数据拷贝到用户空间中,所以零拷贝的出现就是为了解决这种问题的。

关于零拷贝提供了两种方式分别是:

  • mmap+write

  • Sendfile

虚拟内存

所有现代操作系统都使用虚拟内存,使用虚拟的地址取代物理地址,这样做的好处是:

  • 一个以上的虚拟地址可以指向同一个物理内存地址。

  • 虚拟内存空间可大于实际可用的物理地址。

利用第一条特性可以把内核空间地址和用户空间的虚拟地址映射到同一个物理地址,这样 DMA 就可以填充对内核和用户空间进程同时可见的缓冲区了。

大致如下图所示:

怎么理解Netty、Kafka中的零拷贝技术

省去了内核与用户空间的往来拷贝,Java 也利用操作系统的此特性来提升性能,下面重点看看 Java 对零拷贝都有哪些支持。

mmap+write 方式

使用 mmap+write 方式代替原来的 read+write 方式,mmap  是一种内存映射文件的方法,即将一个文件或者其他对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对应关系。

这样就可以省掉原来内核 Read 缓冲区 Copy 数据到用户缓冲区,但是还是需要内核 Read 缓冲区将数据 Copy 到内核 Socket  缓冲区。

大致如下图所示:

怎么理解Netty、Kafka中的零拷贝技术

Sendfile 方式

Sendfile 系统调用在内核版本 2.1 中被引入,目的是简化通过网络在两个通道之间进行的数据传输过程。

Sendfile 系统调用的引入,不仅减少了数据复制,还减少了上下文切换的次数,大致如下图所示:

怎么理解Netty、Kafka中的零拷贝技术

数据传送只发生在内核空间,所以减少了一次上下文切换;但是还是存在一次 Copy,能不能把这一次  Copy 也省略掉?

linux2.4 内核中做了改进,将 Kernel buffer 中对应的数据描述信息(内存地址,偏移量)记录到相应的 Socket  缓冲区当中,这样连内核空间中的一次 CPU Copy 也省掉了。

Java 零拷贝

MappedByteBuffer

Java NIO 提供的 FileChannel 提供了 map() 方法,该方法可以在一个打开的文件和 MappedByteBuffer  之间建立一个虚拟内存映射。

MappedByteBuffer 继承于 ByteBuffer,类似于一个基于内存的缓冲区,只不过该对象的数据元素存储在磁盘的一个文件中。

调用 get() 方法会从磁盘中获取数据,此数据反映该文件当前的内容,调用 put()  方法会更新磁盘上的文件,并且对文件做的修改对其他阅读者也是可见的。

下面看一个简单的读取实例,然后再对 MappedByteBuffer 进行分析:

public class MappedByteBufferTest {      public static void main(String[] args) throws Exception {         File file = new File("D://db.txt");         long len = file.length();         byte[] ds = new byte[(int) len];         MappedByteBuffer mappedByteBuffer = new FileInputStream(file).getChannel().map(FileChannel.MapMode.READ_ONLY, 0,                 len);         for (int offset = 0; offset < len; offset++) {             byte b = mappedByteBuffer.get();             ds[offset] = b;         }         Scanner scan = new Scanner(new ByteArrayInputStream(ds)).useDelimiter(" ");         while (scan.hasNext()) {             System.out.print(scan.next() + " ");         }     } }

主要通过 FileChannel 提供的 map() 来实现映射,map() 方法如下:

public abstract MappedByteBuffer map(MapMode mode,                                          long position, long size)         throws IOException;

分别提供了三个参数,MapMode,Position 和 Size,分别表示:

  • MapMode:映射的模式,可选项包括:READ_ONLY,READ_WRITE,PRIVATE。

  • Position:从哪个位置开始映射,字节数的位置。

  • Size:从 Position 开始向后多少个字节。

重点看一下 MapMode,前两个分别表示只读和可读可写,当然请求的映射模式受到 Filechannel  对象的访问权限限制,如果在一个没有读权限的文件上启用 READ_ONLY,将抛出 NonReadableChannelException。

PRIVATE 模式表示写时拷贝的映射,意味着通过 put() 方法所做的任何修改都会导致产生一个私有的数据拷贝并且该拷贝中的数据只有  MappedByteBuffer 实例可以看到。

该过程不会对底层文件做任何修改,而且一旦缓冲区被施以垃圾收集动作(garbage collected),那些修改都会丢失。

大致浏览一下 map() 方法的源码

public MappedByteBuffer map(MapMode mode, long position, long size)        throws IOException    {            ...省略...            int pagePosition = (int)(position % allocationGranularity);            long mapPosition = position - pagePosition;            long mapSize = size + pagePosition;            try {                // If no exception was thrown from map0, the address is valid                addr = map0(imode, mapPosition, mapSize);            } catch (OutOfMemoryError x) {                // An OutOfMemoryError may indicate that we've exhausted memory                // so force GC and re-attempt map                System.gc();                try {                    Thread.sleep(100);                } catch (InterruptedException y) {                    Thread.currentThread().interrupt();                }                try {                    addr = map0(imode, mapPosition, mapSize);                } catch (OutOfMemoryError y) {                    // After a second OOME, fail                    throw new IOException("Map failed", y);                }            }             // On windows, and potentially other platfORMs, we need an open            // file descriptor for some mapping operations.            FileDescriptor mfd;            try {                mfd = nd.duplicateForMapping(fd);            } catch (IOException ioe) {                unmap0(addr, mapSize);                throw ioe;            }             assert (iOStatus.checkAll(addr));            assert (addr % allocationGranularity == 0);            int isize = (int)size;            Unmapper um = new Unmapper(addr, mapSize, isize, mfd);            if ((!writable) || (imode == MAP_RO)) {                return Util.newMappedByteBufferR(isize,                                                 addr + pagePosition,                                                 mfd,                                                 um);            } else {                return Util.newMappedByteBuffer(isize,                                                addr + pagePosition,                                                mfd,                                                um);            }     }

大致意思就是通过 Native 方法获取内存映射的地址,如果失败,手动 GC 再次映射。

最后通过内存映射的地址实例化出 MappedByteBuffer,MappedByteBuffer 本身是一个抽象类,其实这里真正实例化出来的是  DirectByteBuffer。

DirectByteBuffer

DirectByteBuffer 继承于 MappedByteBuffer,从名字就可以猜测出开辟了一段直接的内存,并不会占用 JVM  的内存空间。

上一节中通过 Filechannel 映射出的 MappedByteBuffer 其实际也是  DirectByteBuffer,当然除了这种方式,也可以手动开辟一段空间:

ByteBuffer directByteBuffer = ByteBuffer.allocateDirect(100);

如上开辟了 100 字节的直接内存空间。

Channel-to-Channel 传输

经常需要从一个位置将文件传输到另外一个位置,FileChannel 提供了 transferTo()  方法用来提高传输的效率,首先看一个简单的实例:

public class ChannelTransfer {     public static void main(String[] argv) throws Exception {         String files[]=new String[1];         files[0]="D://db.txt";         catFiles(Channels.newChannel(System.out), files);     }      private static void catFiles(WritableByteChannel target, String[] files)             throws Exception {         for (int i = 0; i < files.length; i++) {             FileInputStream fis = new FileInputStream(files[i]);             FileChannel channel = fis.getChannel();             channel.transferTo(0, channel.size(), target);             channel.close();             fis.close();         }     } }

通过 FileChannel 的 transferTo() 方法将文件数据传输到 System.out 通道,接口定义如下:

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

几个参数也比较好理解,分别是开始传输的位置,传输的字节数,以及目标通道;transferTo()  允许将一个通道交叉连接到另一个通道,而不需要一个中间缓冲区来传递数据。

注:这里不需要中间缓冲区有两层意思:第一层不需要用户空间缓冲区来拷贝内核缓冲区,另外一层两个通道都有自己的内核缓冲区,两个内核缓冲区也可以做到无需拷贝数据。

Netty 零拷贝

Netty 提供了零拷贝的 Buffer,在传输数据时,最终处理的数据会需要对单个传输的报文,进行组合和拆分,NIO 原生的 ByteBuffer  无法做到,Netty 通过提供的 Composite(组合)和 Slice(拆分)两种 Buffer 来实现零拷贝。

看下面一张图会比较清晰:

怎么理解Netty、Kafka中的零拷贝技术

tcpHttp 报文被分成了两个 ChannelBuffer,这两个 Buffer 对我们上层的逻辑(HTTP 处理)是没有意义的。

但是两个 ChannelBuffer 被组合起来,就成为了一个有意义的 HTTP 报文,这个报文对应的  ChannelBuffer,才是能称之为“Message”的东西,这里用到了一个词“Virtual Buffer”。

可以看一下 Netty 提供的 CompositeChannelBuffer 源码:

public class CompositeChannelBuffer extends AbstractChannelBuffer {      private final ByteOrder order;     private ChannelBuffer[] components;     private int[] indices;     private int lastAccessedComponentId;     private final boolean gathering;      public byte getByte(int index) {         int componentId = componentId(index);         return components[componentId].getByte(index - indices[componentId]);     }     ...省略...

Components 用来保存的就是所有接收到的 Buffer,Indices 记录每个 buffer  的起始位置,lastAccessedComponentId 记录上一次访问的 ComponentId。

CompositeChannelBuffer 并不会开辟新的内存并直接复制所有 ChannelBuffer 内容,而是直接保存了所有  ChannelBuffer 的引用,并在子 ChannelBuffer 里进行读写,实现了零拷贝。

其他零拷贝

RocketMQ 的消息采用顺序写到 commitlog 文件,然后利用 consume queue 文件作为索引

RocketMQ 采用零拷贝 mmap+write 的方式来回应 Consumer 的请求。

同样 Kafka 中存在大量的网络数据持久化到磁盘和磁盘文件通过网络发送的过程,Kafka使用了 Sendfile 零拷贝方式。

“怎么理解Netty、Kafka中的零拷贝技术”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

--结束END--

本文标题: 怎么理解Netty、Kafka中的零拷贝技术

本文链接: https://lsjlt.com/news/85205.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 怎么理解Netty、Kafka中的零拷贝技术
    本篇内容介绍了“怎么理解Netty、Kafka中的零拷贝技术”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!...
    99+
    2024-04-02
  • kafka零拷贝技术怎么应用
    Kafka零拷贝技术是一种优化技术,可以提高数据传输的效率,减少数据在内存和磁盘之间的拷贝次数。它的应用主要体现在以下几个方面: ...
    99+
    2023-10-27
    kafka
  • 什么是零拷贝技术
    本篇内容介绍了“什么是零拷贝技术”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、什么是零拷贝1、从一个案...
    99+
    2024-04-02
  • 零拷贝Zero-Copy技术如何理解
    这篇文章将为大家详细讲解有关零拷贝Zero-Copy技术如何理解,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。 1.前言今天和大家一起学习个底层技术点-零拷贝Zero-Copy。L...
    99+
    2023-06-15
  • Linux中零拷贝技术是什么
    这篇文章给大家分享的是有关Linux中零拷贝技术是什么的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。引文在写一个服务端程序时(Web Server或者文件服务器),文件下载是一个基本功能。这时候服务端的任务是:将...
    99+
    2023-06-15
  • kafka零拷贝的原理是什么
    Kafka的零拷贝(Zero-Copy)原理是通过避免数据在内核态和用户态之间的多次拷贝来提高性能和效率。在传统的网络数据传输过程中...
    99+
    2023-10-21
    kafka
  • golang零拷贝技术怎么使用
    Go语言中的零拷贝技术主要通过使用unsafe.Pointer和reflect.SliceHeader来实现。下面是一个使用零拷贝技...
    99+
    2023-10-23
    golang
  • Linux零拷贝技术的用法
    这篇文章主要讲解了“Linux零拷贝技术的用法”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Linux零拷贝技术的用法”吧!       1、为什么需...
    99+
    2023-06-05
  • 浅谈Linux的零拷贝技术
    前言 在linux系统内部缓存和内存容量都是有限的,更多的数据都是存储在磁盘中。对于Web服务器来说,经常需要从磁盘中读取数据到内存,然后再通过网卡传输给用户: 那么这也算一次I O的过程,都知道IO过程中需要状态的切...
    99+
    2023-04-28
    Linux 零拷贝 零拷贝技术
  • 对于Netty ByteBuf的零拷贝的理解是怎样的
    这篇文章给大家介绍对于Netty ByteBuf的零拷贝的理解是怎样的,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。根据 Wiki 对 Zero-copy 的定义:"Zero-copy" descr...
    99+
    2023-06-17
  • linux下的零拷贝技术介绍
    本篇内容主要讲解“linux下的零拷贝技术介绍”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“linux下的零拷贝技术介绍”吧!传统的数据传输方式很长一段时间内,数据拷贝的认识仅仅停留在应用程序层...
    99+
    2023-06-13
  • 浅析Linux中的零拷贝技术的使用
    本文探讨Linux中主要的几种零拷贝技术以及零拷贝技术适用的场景。为了迅速建立起零拷贝的概念,我们拿一个常用的场景进行引入: 引文## 在写一个服务端程序时(Web Server或者文件服务器),文件下载是一个基本...
    99+
    2022-06-03
    Linux 零拷贝
  • Java中浅拷贝和深拷贝该怎么理解
    这篇文章给大家介绍Java中浅拷贝和深拷贝该怎么理解,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。Java浅拷贝深拷贝浅拷贝和深拷贝涉及到了Object类中的clone()方法实现浅拷贝浅拷贝的实现需要类重写clone...
    99+
    2023-06-21
  • 怎么理解python指针拷贝,浅拷贝和深拷贝
    本篇内容主要讲解“怎么理解python指针拷贝,浅拷贝和深拷贝”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么理解python指针拷贝,浅拷贝和深拷贝”吧!首先对于不可变类型int,strin...
    99+
    2023-06-02
  • 怎么理解Javascript深拷贝与浅拷贝
    怎么理解Javascript深拷贝与浅拷贝,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。 前言在 javascript &nbs...
    99+
    2024-04-02
  • 深入理解python中的浅拷贝和深拷贝
    在讲什么是深浅拷贝之前,我们先来看这样一个现象: a = ['scolia', 123, [], ] b = a[:] b[2].append(666) print a print b 为什么我只对...
    99+
    2022-06-04
    和深 python
  • 如何理解JavaScript中的浅拷贝与深拷贝
    本篇文章给大家分享的是有关如何理解JavaScript中的浅拷贝与深拷贝,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。 浅拷贝在使用JavaScript对数组进行操作...
    99+
    2023-06-16
  • Netty对JDK缓冲区中内存池零拷贝优化的示例分析
    这篇文章主要介绍Netty对JDK缓冲区中内存池零拷贝优化的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!NIO中缓冲区是数据传输的基础,JDK通过ByteBuffer实现,Netty框架中并未采用JDK原生...
    99+
    2023-06-04
  • Python中Numpy的深拷贝和浅拷贝怎么使用
    这篇“Python中Numpy的深拷贝和浅拷贝怎么使用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Python中Numpy...
    99+
    2023-06-30
  • 怎么解决JavaScript的深浅拷贝
    这篇文章主要讲解了“怎么解决JavaScript的深浅拷贝”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么解决JavaScript的深浅拷贝”吧!正文从一则故事讲起,昨天因为医院开不出药,...
    99+
    2023-06-27
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作