当我们了解消息队列中间件包括但是不限于rocketMQ,kafaka之类的时候总会碰到它在IO方面做出的众多“黑魔法”的相关介绍,包括什么零拷贝啦,内存映射技术啦。。。
那么这些黑魔法究竟怎么一回事呢?那还要从最底层的LinuxIO栈结构说起,这里先来张简图
我们经常接触到的IO方式基本是以下三种:
1.BuffIO
这是最常使用的IO方式,也就是说当我们发起IO调用
当READ时内核接收到我们的请求,先命中buffIO命中则返回,无法命中则去请求块IO层这是CPU向DMA发出指令,DMA接管总线和坐标,开始拷贝磁盘中的数据到buff缓冲区,整个过程是没有CPU参与的这也是DMA的优点之一,当拷贝完成时像CPU发送中断指令,CPU接收开始拷贝内核数据至用户数据页,切换到用户态读取完成
当WRITE时则同理发生两次拷贝,用户页->内核->DMA拷贝
2.MMap
内存映射技术是在上述基础上直接将pagecache内容映射到用户空间的页地址,整个过程只发生一次拷贝,但是同时要求我们是块IO,因为本质还是调用了块IO这一层
3.DirectIO
直接IO是不用DMA拷贝数据到内核内存空间,而是直接建立用户空间与块IO之间的内存拷贝,掠过pageCache,但是根本问题出现了,那就是首次IO效率会比较高,但是每次直接与DMA交互进行扇区拷贝,如果没有自己建立缓存的换相对来说会更低效。
再渗透下各个层的知识
T1.
VFS是Linux的虚拟文件系统,再Linux中万物皆文件,也就是说对套接字的R/W操作实际是对socket的文件句柄进行IO操作,那么必然要经过VFS层,虽然否属于文件但是不同设备底层本质肯定是不同的也就是说对应各个文件系统,那么VFS就是管理这些文件系统,在不同的分区使用不同的文件系统但是对于用户来说是无感的
T2.
BlockIO层,块IO层,在计算机系统中,块IO设计代表磁盘等不可随机存取的设备,也就是说必须是顺序按块存取的,这里读取的块信息叫做bufCache,pageCache时间上是块设备IO结果的上层映射,在这一层IO调度中会采用不同的磁盘调度算法进行请求的排序和重组,因为块设备的IO是不支持随机存储的所以基本采取近邻原则(其实这里与innoDB的Join算法相近了)
T3.
DeviceIO这一层就是最底层的设备层了,DMA在这一层也就是IO过程中的最底层调用,要访问相应的IO设备了,那有了DMA的出现CPU直接向DMA发起指令也就节约了cpu向硬盘发出读写过程在一定程度上节约了CPU算力。
最后还要补充一点,除了MMap和DirectIO消息队列最常用的还有sendfile,这个技术是直接在内核中把磁盘IO的结果直接从buf拷贝到socket套接字的缓冲区中然后DMA拷贝到套接字的协议引擎中,当然进一步优化就是直接让DMA拷贝缓冲区的数据到协议引擎中,整个过程只发生两次拷贝,这里的零指的是用户空间的零拷贝,直接在内核中完成。