Redis 是 在内存存储数据的,数据读取时不要经过磁盘的IO,只需要内存的操作,这也是redis访问速度快的原因
Kafka背道而驰,Kafka 是在磁盘存储数据的
,发送过来的数据交给Kafka后会落盘,消费者读取数据时,也是通过磁盘读取,发送给消费者
Kafka高性能,是多方面协同的结果:
- 宏观架构
- 分布式partition存储
- ISR数据同步
- 高效利用磁盘/操作系统特性
零拷贝
,不是不需要拷贝,而是减少不必要的拷贝次数
,通常是说在IO读写过程中
传统IO: 4次拷贝
用户态
:运行中的用户的程序内核态
:通常指操作系统
例如: 当程序发送数据给消费者,如下图:
- 需要从磁盘去读数据
- Kafka需要调用Java的API,API运行在JVM上
- JVM再去调用操作系统内核的函数
- 内核利用驱动从磁盘去读取数据
- 驱动从磁盘读取数据后,将数据
拷贝
到内核缓冲区 - 再将数据从内核缓存区
拷贝
到应用程序,涉及到内核态到用户态的转换
,转换的速度往往比较慢 - 再调用操作系统的函数,将数据
拷贝
到内核缓冲区(Socket网络驱动缓冲区) - 然后将socket buffer的数据
拷贝
到网络协议栈,由操作系统驱动网卡进行网络传输,这会用到七层网络模型
整体就是读数据拷贝两次,将读到的数据发送出去再拷贝两次
操作系统中非常多的地方用到了缓存,为了提高操作系统处理的性能,操作系统发展了七八十年,已经发展的非常成熟了,操作系统内部大量使用缓存技术,CPU、硬盘等硬件上的缓存,操作系统处理数据是大量、高速去处理的,比如文件的拷贝,网络下载内容,玩游戏等, 速度非常快
应用程序就没有那么快了, 我们写的API是操作数据的,int类型变量占8个字节,long型占16个字节,double类型占16个字节 ,按字节来算的
内核操作是分块的,叫buffer操作 ,一批一批的操作,速度非常快
传统IO 的4次拷贝是存在不必要的拷贝的,
实际上并不需要第二个和第三个数据副本,数据可以直接从读缓冲区传输到socket buffer
Kafka 的零拷贝:
- 网络数据持久化到磁盘(Producer 到 Broker)
- 磁盘文件通过网络发送(Broker 到 Consumer)
- 减少了用户态与内核态的上下文切换
盗张图,图画的很通俗易懂了
磁盘数据通过DMA(直接存储器访问),
拷贝到内核态buffer 直接通过DMA拷贝到socket buffer,
无需cpu拷贝
除了减少数据拷贝次数外,整个读数据到网络发送的过程由一个sendFile()
方法完成,整个过程只有两次上下文切换,因此也大大提高了性能
Kafka 通过Java NIO
对sendFile的支持,
通过调用FileChannel的transferTo()和transferFrom()
方法,实现的零拷贝
transferTo()方法直接将字节从它被调用的通道上传输到另外一个可写字节通道上,数据无需经过应用程序,在内部,零拷贝需要底层操作系统支持,Linux 2.4
以后的版本是支持的
FileChannal.transferTo() 底层就是实现sendFile()方法
#include <sys/socket.h>ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
零拷贝(Zero-Copy):
利用直接内存访问(Direct Memory Access,DMA)的能力,是一种硬件技术
DMA允许外部设备(如网卡)直接访问内存,绕过CPU的用户态和内核态的转换,实现高速数据传输