java zero copy 实现,关于Zero Copy

概述

很多web应用都会有大量的静态文件。我们通常是从硬盘读取这些静态文件,并将完全相同的文件数据写到response socket。这样的操作需要较少的CPU,但是效率有些低,它需要经过如下的过程:kernel从硬盘读取数据,越过kernel-user边界将数据传递给用户空间的web应用;用户空间的web应用再次越过kernel-user边界将完全相同的数据写回到kernel空间的socket。在将数据从硬盘传递到socket的过程中,用户空间web应用的角色相当于一个中介,并且有些低效。

数据每次经过kernel-user边界的时候,都需要被copy一次,这样会消耗CPU资源及内存带宽。幸运的是,我们可以使用一种被称为zero copy的技术来消除这些copy操作。使用zero copy技术的应用会请求kernel直接将数据从硬盘拷贝到socket,而无需再经过应用。zero copy极大地提升了应用的性能,并减少了内核态和用户态上下文切换的次数。

在Linux和UNIX系统上,Java类库通过java.nio.channels.FileChannel的transferTo()方法实现了对zero copy的支持。我们可以使用transferTo()方法将读取到的字节数组直接从被调用的channel传输到另一个可写的channel上,这个过程中数据流转不需要通过应用。

接下来我们会先讲解一下如何使用传统的多次copy的机制实现数据的传输,而后再演示下使用transferTo()方法实现的zero copy技术是如何提升性能的。

传统数据传输方案

思考一下如下的场景:从一个文件读取数据,通过网络将数据传递给另一个应用程序(这个场景描述了大部分服务器应用的行为,包括处理静态文件的WEB服务器,FTP服务器,Mail服务器等)。这个操作的核心步骤只有两步,我们看下代码:

1

2

File.read(fileDesc,buf,len);

Socket.send(socket,buf,len);

我们的代码只有两行,看起来很简单,但是服务器完成这个过程却需要在用户态和内核态之间进行4次上下文切换,也就是说在这个操作完成之前数据需要被copy 4次。下面的图片展示了服务器是如何将数据从文件传输到socket的。

图一:传统模式下数据拷贝过程:

cb3b6c09520d322688813419609b8af4.gif

图二:传统模式下内核态和用户态之间的上下文切换

ba430559e24826cb355d2eebd5636d57.gif

涉及到的步骤包括:

调用read()方法导致了用户态到内核态的切换(参看图二)。在系统内部是通过sys_read()(或类似的其他方法)从文件读取数据。第一次copy(参看图一)是通过直接内存访问(DMA)引擎实现的,这次copy从硬盘上读取了文件内容并将之保存在内核空间的缓冲区中。

第二次copy发生在数据从内核缓冲区被copy到用户缓冲区时,此时read()方法也返回了。read()方法的返回导致了从内核态到用户态一次切换。现在数据是保存在用户空间的缓冲区中。

socket调用send()方法再次引起了用户态到内核态的切换。第三次copy再次将数据放回到内核缓冲区。不过这次的内核缓冲区和上次的不同,这次的缓冲区和目标socket相关。

调用的send()方法返回时,产生了内核态到用户态的上下文切换。这次DMA引擎将数据从内核缓冲区发送到protocol引擎,也就是第四次copy,这是一个独立异步的操作。

使用内核缓冲区作为中间层(而不是直接将数据传送到用户缓冲区)可能看起来有些低效。但是最初将内核缓冲区作为中间层引入进程的目的就是提升性能。在读取数据的时候,作为中间层的内核缓冲区的角色相当于“预读取缓存”,也就是说如果应用请求的数据量比内核缓冲区空间小,就会将一部分数据预读取到作为中间层的内核缓冲区中以供下一次请求使用。很显然,在请求的数据量比内核缓冲区空间小时,这样做可以显著地提升应用性能。在写数据的时候,多个中间层有助于更好地实现异步写(先将数据写到中间缓存,中间层快满时再批量写出)。

不幸的是,在请求的数据量大过内核缓冲区很多时,这种方法本身也会成为性能瓶颈:因为数据会在硬盘、内核缓冲区和用户缓冲区之间多次拷贝。

zero copy可以排除这些多余的copy来提升性能。

zero copy方案

重新思考一下传统的数据传输方案,将会发现第二次和第三次的copy行为实际上是不必要的。在传统方案里,应用做的事情只不过是缓存数据并将之转发到socket缓冲区,我们可以考虑直接将数据从读缓存发送到socket缓冲区中。transferTo()方法能让我们实现这种操作。

transferTo()方法的定义如下:

1

publicvoidtransferTo(longposition,longcount,WritableByteChanneltarget);

transferTo()方法可以将数据从FileChannel发送到指定的WritableByteChannel中。transferTo()方法需要依赖底层操作系统的支持才能实现zero copy。在UNIX系统和各种Linux系统中,支持zero copy的系统方法是sendfile(),这个方法可以将数据从一个文件描述符转发到另一个文件描述符中。

sendfile()方法定义:

1

2

#include

ssize_tsendfile(intout_fd,intin_fd,off_t*offset,size_tcount);

在概述中,我们写过两行代码演示传统数据传输的方法,演示代码中的file.read()和socket.send()两个方法的调用可以替换为调用transferTo()方法,示例如下:

1

transferTo(position,count,writableChannel);

下图演示了调用transferTo()方法时数据传输的路径:

1d3f9e6c482791e25def598cc3ee056d.gif

下图演示了调用transferTo()方法时用户态和内核态上下文切换的过程:

01b7973e6c2d0dfde4640c8d51c391f9.gif

调用transferTo()方法涉及到的步骤为:

调用transferTo()方法产生了第一次copy:DMA引擎将文件内容copy到了读缓存中。

然后系统内核将数据copy到与输出socket相关的内核缓冲区中。

第三次copy发生在DMA引擎将数据从内核socket缓冲区发送到protocol引擎时。

看看效果:

将用户态-内核态上下文切换由四次减少到了两次;

将数据的copy由四次减少到了三次(其中只有一次涉及到CPU)。

不过这样子还没有达到使用zero copy的目标。如果底层网卡支持收集操作的话,我们还可以去掉由内核完成的copy(即第二次copy)。在Linux Kernel2.4及以后的版本中,socket缓冲区描述符已经被调整到满足这种需求了。这样这个方案不仅仅是减少了上下文切换的次数,也消除了copy过程中对CPU依赖的部分。尽管用户还是在用transferTo()方法,但是其底层行为已经发生了变化:

调用transferTo()方法时,DMA引擎将文件内容copy到内核缓冲区中;

不再将数据copy到socket缓冲区中,只是将数据描述符(包含地址信息和长度信息)追加到socket缓冲区。DMA引擎直接将数据从内核缓冲区传递到protocol引擎,从而消除了仅剩的CPU copy。

下图展示了使用transferTo()方法和收集操作时copy的详情:

28d7a993a2fed02c39c3ce98c76bfc2d.gif

构建文件服务器

现在我们练习使用一下zero copy,就演示一下文件在客户端和服务器之间的传递(示例代码下载地址见文末)。TraditionalClient.java以及TraditionalServer.java是基于传统方案的实现,和新方法是File.read()和Socket.send()。TraditionalServer.java是一个Server端程序,它监听着一个特定的端口以让Client连接,每次会从socket读取4KB数据。TraditionalClient.java连接到Server上,从一个文件中读取(使用File.read()方法)4KB数据并通过socket将数据发送(使用Socket.send()方法)给Server。

类似的,TransferToServer.java和TransferToClient.java实现了相同的功能,不过使用的是transferTo()方法(调用了系统的sendfile()方法),将文件数据从Server端发送到了Client端。

性能比较

我们在一台Linux Kernel版本2.6的机器上执行了示例代码,以毫秒级的时间尺度比较了传统方案和transferTo()方案传输不同大小的数据文件的速度。下表为测试结果:

File size

Normal file transfer (ms)

transferTo (ms)

7MB

156

45

21MB

337

128

63MB

843

387

98MB

1320

617

200MB

2124

1150

350MB

3631

1762

700MB

13498

4422

1GB

18399

8537

可以看到,较之传统方案,transferTo() API降低了大约65%的时间消耗。对于需要在IO channel间进行大量数据copy和传输的应用(比如WebServer),transferTo()可以显著地提升性能。

总结

我们演示了使用transferTo()的性能优势,可以看到中间缓冲区copy(即使是发生在内核中)会有一定的性能损失。对于需要进行channel间大量数据copy的应用,zero copy技术可以显著地提升性能。

其他

###################

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/398013.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java量与变量的区别

2019独角兽企业重金招聘Python工程师标准>>> 常量:其值不变即为常量。 语法: 数据类型 常量名 值; double PI 3.14;    备注: 一般默认常量名大写。 变量与常量之间关系(量间关系) 先来一个简单的实例,好了解 Java…

团队作业6

Alpha版本展示 一、 刘阳航|201521123026(组长) 博客地址:http://www.cnblogs.com/lyhooo/ 负责图形的创建和移动部分, 游戏区操作的实现,部分算法的编写 陈文俊|201521123047 博客地址:http://www.cnblogs…

java 新浪短网址生成器,新浪短链接接口被限制?最新新浪短网址api接口

背景新浪短网址api是sina平台官对外公开的短网址生成接口,可以将长链接通过接口生成t.cn样式的短链接,可以说是非常好用的。但近期新浪官方开始对已经公布的接口做出了多重限制,很多之前能用的功能现在都频频被限制,甚至有的时候接…

Sql Server 部署SSIS包完成远程数据传输

本篇介绍如何使用SSIS和作业完成自动更新目标数据任务。 ** 温馨提示:如需转载本文,请注明内容出处。** 本文链接:https://www.cnblogs.com/grom/p/9018978.html 笔者需要定期从服务器更新N家客户的远程服务器数据,上一篇的存储过程是其中一…

java值类型和引用类型 == 比较,Java中值类型和引用类型的比较与问题解决

一、问题描述前几天因为一个需求出现了Bug。说高级点也挺高级,说白点也很简单。其实也就是一个很简单的Java基础入门时候的值类型和引用类型的区别。只是开发的时候由于自己的问题,导致小问题的出现。还好突然想起来以前看过一篇对于该问题讲解的博客&am…

用最简单的例子说明设计模式(三)之责任链、建造者、适配器、代理模式、享元模式...

责任链模式一个请求有多个对象来处理,这些对象是一条链,但具体由哪个对象来处理,根据条件判断来确定,如果不能处理会传递给该链中的下一个对象,直到有对象处理它为止使用场景1)有多个对象可以处理同一个请求&#xff0…

利用Java针对MySql封装的jdbc框架类 JdbcUtils 完整实现(包含增删改查、JavaBean反射原理,附源码)...

最近看老罗的视频,跟着完成了利用Java操作MySQL数据库的一个框架类JdbcUtils.java,完成对数据库的增删改查。其中查询这块,包括普通的查询和利用反射完成的查询,主要包括以下几个函数接口: 1、public Connection getConnection() 获得数据库…

Linux启动提示Kernel panic - not syncing: Attempted to kill init解决办法

系统类型:CentOS 6.5(x64) 启动提示:Kernel panic - not syncing: Attempted to kill init 解决办法: 系统启动的时候,按下‘e’键进入grub编辑界面,编辑grub菜单,选择“kernel /vmlinuz-XXXXro root/dev/v…

vuex和vuejs

前言:在最近学习 Vue.js 的时候,看到国外一篇讲述了如何使用 Vue.js 和 Vuex 来构建一个简单笔记的单页应用的文章。感觉收获挺多,自己在它的例子的基础上进行了一些优化和自定义功能,在这里和大家分享下学习心得。 在这篇教程中我…

laravel mysql 配置,laravel5数据库配置及其注意事项

今天分享一个Laravel5数据库配置上的坑。Laravel5作为一套简洁、优雅的PHP Web开发框架(笑),唯一不足的一点就是中文手册或者说是资料比较少,虽然现在很多大神也开始普及这些东西,但是大神一遍也会忽略一下小坑。今天配置了一下数据库&#x…

React开发中常用的工具集锦

本文从属于笔者的React入门与最佳实践系列。本文记录了笔者在React开发中常见的一些工具插件,如果你想寻找合适的项目生成器或者模板,请参考笔者的使用Facebook的create-react-app快速构建React开发环境 React Devtools React Devtools是React官方提供的…

9-[记录操作]--数据的增删改,权限管理

1、数据操作语言: DML(data manage language) 在MySQL管理软件中,可以通过SQL语句中的DML语言来实现数据的操作,包括 使用INSERT实现数据的插入UPDATE实现数据的更新使用DELETE实现数据的删除使用SELECT查询数据以及。…

hdu 1023 Train Problem II

题目连接 http://acm.hdu.edu.cn/showproblem.php?pid1212 Train Problem II Description As we all know the Train Problem I, the boss of the Ignatius Train Station want to know if all the trains come in strict-increasing order, how many orders that all the tra…

Nginx自建CDN加速节点 实现DNS智能解析网站项目

如今,网站项目越来越多的会使用CDN加速,如果需要便捷一点的可以直接用第三方提供的CDN加速服务,比如百度CDN、七牛、又拍云、腾讯云、阿里云等等服务商都有提供这类服务。但是前提条件是需要一定的成本,以及网站域名是需要BA才可以…

matlab中获取view,ios 怎么获取一个view的位置

ios 怎么获取一个view的位置以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!ios 怎么获取一个view的位置打开appstore进入应用,右上角的分享按钮(从右往左数第二个),拷…

eureka需要替换吗_Spring Cloud Alibaba迁移指南1:零代码从Eureka迁移到Nacos

作者:得少,校对:周立。在本号首发,欢迎转载。Spring Cloud官方宣布Spring Cloud Netflix进入维护状态,后续不再会有新的功能已成为事实。作为开发者,如何使用极简的方式替换Netflix相关组件成为首要解决的问…

JAVA TCP通信练习

2019独角兽企业重金招聘Python工程师标准>>> 1、Server端 package com.hhdys.serviceimpl;import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.List; import java.util.Scanner; import java.…

微服务架构下的身份认证

从单体应用架构到分布式应用架构再到微服务架构,应用的安全访问在不断的经受考验。为了适应架构的变化、需求的变化,身份认证与鉴权方案也在不断的变革。面对数十个甚至上百个微服务之间的调用,如何保证高效安全的身份认证?面对外…

php缓存注入,利用Thinkphp 5缓存漏洞实现前台Getshell

原标题:利用Thinkphp 5缓存漏洞实现前台Getshell*本文原创作者:WindWing,属于FreeBuf原创奖励计划,禁止转载000 背景网站为了实现加速访问,会将用户访问过的页面存入缓存来减小数据库查询的开销。而Thinkphp5框架的缓存…

mac版小达人点读包怎么安装_小达人点读笔扩容实战:16G变128G

随着小达人点读笔可以点读的童书越来越多,笔的容量就是个尴尬的问题:是处理掉手头的16G容量的旧点读笔,重新再买32G容量的新点记笔吗?如果32G容量也不够用怎么办?官方可没有更大容量的点读笔了。删除原先的点读内容&am…