e.Tomcat中的sendfile支持

sendfile实质是linux系统中一项优化技术,用以发送文件和网络通信时,减少用户态空间与磁盘倒换数据,而直接在内核级做数据拷贝,这项技术是linux2.4之后就有的,现在已经很普遍的用在了C的网络端服务器上了,而对于java而言,因为java是高级语言中的高级语言,至少在C语言的层面上可以提供sendfile级别的接口,举个例子,java中可以通过jni的方式调用c的库,而这种在tomcat中其实就是APR通道,通过tomcat-native去调用类似于APR库,这种调用思路虽然增大了java调用链条,但可以在java层级中获得如sendfile的这种linux系统级优化的支持,可谓是一举多得。

上述的内容,实际就是本文的背景,本文就从系统调用的层级,逐步讲解tomcat中的sendfile是怎么实现的。

1. 介绍linux的sendfile机制

sendfile是一个系统调用,可以man一下,看到其函数细节:

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

sendfile()是作用于数据拷贝在两个文件描述符之间的操作函数.这个拷贝操作是内核中操作的,所以称为"零拷贝".sendfile函数比起read和write函数高效得多,因为read和write是要把数据拷贝到用户应用层操作.

参数说明:
out_fd 是已经打开了,用于写操作(write)的文件描述符;
in_fd 是已经打开了,用于读操作(read)的文件描述符;
offset 偏移量;表示sendfile函数从in_fd中的哪一偏移量开始读取数据.如果是零表示从文件的开始读,否则从相应的便宜量读取.如果是循环读取的时候,下一次offset值应为sendfile函数返回值加上本次的offset的值.
count是在两个描述符之间拷贝的字节数(bytes)

返回值:
如果成功的拷贝,返回写操作到out_fd的字节数,错误返回-1,并相应的设置error信息.
EAGAIN 无阻塞I/O设置O_NONBLOCK时,写操作(write)阻塞了.
EBADF 输出或者输入的文件描述符没有打开.
EFAULT 错误的地址.
EINVAL 描述符不可用或者锁定了,或者用mmap()函数操作的in_fd不可用.
EIO 当读取(read)in_fd时发生未知错误.
ENOMEM 读(read)in_fd时内存不足.

总结一下,实际sendfile就是一个高效的函数,用于替换write,我们看看程序和普通的网络send系统调用程序的区别:

发送端传统方式代码段如下:
fd = open(FILENAME, O_RDONLY);
while((len =read(fd, buff, sizeof(buff))) >0) 
{
send(sockfd, buff, len ,0);
}
close(fd);

再看看使用sendfile:

使用sendfile()传输代码段.
off_t offset = 0;
stat(FILENAME, &filestat);

fd = open(FILENAME, O_RDONLY);
sendfile(sockfd, fd, &offset, filestat.st_size) );
close(fd);

sendfile调用和send调用非常类似,很容易就可以在API级别进行替换,程序几乎不用修改什么东西,而换成了高级的优化调用,再高并发的场景下,会明显从数据看出来,性能提升不少。

2.sendfile优化效果

介绍完系统调用之后,来看看sendfile究竟优化省略了哪几步的操作,

传统的网络发送请求,肯定会走内存,因为内存中可能有一些数据需要处理,例如数据的一些加工啊,抽取之类的,

但对于纯文本字符,例如文件这种的,不需要进行修改,直接发送,那么其实就没有必要再走内存了,也就是上面的方框部分虚线引入的部分直接就不走了,直接可以从磁盘到内核缓冲区,然后在内核级别直接将这块数据流转到网卡缓冲区,然后直接由网络介质发送了,可见,这就是sendfile的功效所在。

因此,sendfile的使用场景,我们也应该非常清楚了,对于例如web服务器中的静态资源,静态文件这种的http请求,不需要再内存之中进行加工的,sendfile是最优的选择。

3.Defaultservlet的sendfile逻辑

对于Tomcat中的静态资源处理,直接对应的就是DefaultServlet了,这个类是嵌入在Tomcat源码中,专门处理静态资源的类,我们来看其比较关键的doget(之后调用的serveReource方法)的源码:

对于上述的代码逻辑是,当checkSendfile方法不为true,说明该请求就是普通的请求,那么按照此逻辑,需要将请求的文件输入到ostream流中,最后将这个流通过从copy方法,转接到Outputstream中,通过网络传输出去。

但是,如果请求request中设置了 org.apache.tomcat.sendfile.support 设为Boolean.TRUE,则表示支持 sendfile,那么这个属性就代表着该reqeuest请求发送的文件吗,是通过sendfile系统调用来进行发送,而不是通过send系统调用(默认的java网络socket发送流,实际jvm底层调用的就是send系统调用)。

对于上述的这个 org.apache.tomcat.sendfile.support 属性来说,相当于每一个request请求都可以通过设置该属性,告诉服务器,我这个请求要使用sendfile,而不是send,这相当于是非常的灵活了。

除了 org.apache.tomcat.sendfile.support 属性,通过代码的分析,还有几个属性也可以在request中进行设置,分别为:

  • org.apache.tomcat.sendfile.filename 作为字符串发送的标准文件名。
  • org.apache.tomcat.sendfile.start开始位置偏移值,长整型值。
  • org.apache.tomcat.sendfile.end 结束位置偏移值,长整型值。

当然,如果不进行设置,那么默认的文件名就是该request请求的文件,start是0,end是length,从上述的代码中也可以看得出来。

我们回头看一下,这几个参数为什么需要设置,对应sendfile的几个参数就可以明白了。

如果是checkSendfile方法为true,那么在DefaultServlet中不进行流的转接,该处理是在Tomcat前端中的不同XXXEndpoint类中,请继续往下看。

值得注意的一点是,一般http响应的数据包都会进行压缩,这样的好处是能极大的减小带宽占用,而响应头中发现了compression压缩属性,浏览器会自动首先进行解压缩,从而正确的将response响应主体刷到页面中。

但是,当sendfile属性开启后,这个compression压缩属性就不生效了,因此,当需要传输的文件非常大的时候,而网络带宽又是瓶颈的时候,sendfile显然并不是合适之举。

4.sendfile在BIO通道中的实现

以Tomcat8为例,不同的Tomcat前端通道中的sendfile的java包装是不同的,但实际上都是在调用系统调用sendfile。

对于,BIO来说,JIOEndpoint是不支持sendfile的,这个可以通过代码中看出来:

5.sendfile在NIO通道中的实现

在NIO通道中,有一个useSendfile属性,这个useSendfile属性是做什么的呢?

这个是可以设置在Connector中的,以NIO通道为例,配置为:

这个useSendfile属性是允许request进行sendfile的总体开关(前面讲的org.apache.tomcat.sendfile.support 属性是针对于每一个request的),这个useSendfile属性在NIO通道中默认就是打开的,当reqeust设置org.apache.tomcat.sendfile.support 属性为true的时候,response就会准备一个SendFileData的数据结构,这个数据结构就是NIO通道下的sendfile的媒介:

这个数据结构是用于传递给sendfile系统调用,用于发送。

因此,NIO的sendfile实现可以分为三个阶段:

第一阶段,实际上就是前面的XXXDefaultServlet中(不仅仅是DefaultServlet,其它的Servlet只要设置这个属性也可以调用sendfile)对Request的sendfile属性的设置,当该请求设置上述的属性后,证明该请求为sendfile请求。

第二阶段,servlet处理完之后,业务逻辑完成,对应的Response该commit了,而在Response的准备阶段,会初始化这个SendFileData的数据结构,这块的代码逻辑都在Http11NioProcessor类中:

从上述的代码逻辑来看,prepareSendfile方法是从前面DefaultServlet中设置的reqeust属性中,拿到file名称,字符位置的start,end,然后将这些属性作为传入的参数,初始化SendFileData实例;

第三阶段,我们记得NIO前端通道的Acceptor,Poller线程,Worker线程的三个线程,当Worker线程干完活之后,返回给客户端,依然要通过Poller线程,也就是会重新注册KeyEvent,读取KeyAttachment,这个时候当为sendfile的时候,前面初始化的SendFileData实例是会注册在KeyAttachment上的:

上述的processSendfile就是Poller线程的run中的一个判断分支,当为sendfile的时候,Poller线程就对SendFileData数据结构中的file名字取出,通过FileChannel的transferTo方法。

对于这个transferTo方法,我们可以看到其中的一个重要的解释:

上述的解释中就是sendfile系统调用。

6.sendfile在APR通道中的实现

在NIO通道中sendfile实现算是比较复杂的了,在APR通道中更加的复杂,我们可以回过头先看看NIO通道中的sendfile,实际是通过每一个Poller线程中的FileChannel的transferTo方法来实现的,对于transferTo方法是阻塞的,这也就意味着,当文件进行sendfile的时候,Poller线程是阻塞的,而我们前面研究过Tomcat前端,Poller线程是很珍贵的,不仅仅是为某几个sendfile服务的,这样会导致Poller线程产生瓶颈,从而拖慢了整个Tomcat前端的效率。

对于APR来讲,基于上述更进一步,通过下面的配置就可以看出端倪:

useSendfile属性没什么可说的,就是全局的sendfile开关;

sendfileThreadCount对应的就是APR通道中,将sendfile的功能从Poller线程中剥离开来,

这相当于sendfileData的数据结构,直接加入到Sendfile线程中了:

好处不言自明,Poller就干Poller的事,而遇到Sendfile的需求的时候,sendfile线程就挺身而出,把活给接了;

最后,对于APR通道是通过JNI调用的APR库,sendfile自然就不是java的API了:

总结:

SendFile实际上是操作系统的优化,Tomcat中基于在不同的通道中有不同的实现,配置也不尽相同,但实际上都是调用操作系统的SendFile的系统调用!

参考资料:http://www.linuxjournal.com/article/6345?page=0,0

来自为知笔记(Wiz)

时间: 2024-12-18 17:53:36

e.Tomcat中的sendfile支持的相关文章

在 Tomcat 中配置 SSL/TLS 以支持 HTTPS

本件详细介绍了如何通过几个简单步骤在 Tomcat 中配置 SSL/TLS .使用 JDK 生成自签名的证书,最终实现在应用中支持 HTTPS 协议. 生产密钥和证书 Tomcat 目前只能操作 JKS.PKCS11.PKCS12 格式的密钥存储库.JKS 是 Java 标准的"Java 密钥存储库"格式,是通过 keytool 命令行工具创建的.该工具包含在 JDK 中.PKCS12 格式一种互联网标准,可以通过 OpenSSL 和 Microsoft 的 Key-Manager 来

用JAXWS-RI在Tomcat中发布WebService

JDK中已经内置了Webservice发布,不过要用Tomcat等Web服务器发布WebService,还需要用第三方Webservice框架.Axis2和CXF是目前最流行的Webservice框架,这两个框架各有优点,不过都属于重量级框架. JAXWS-RI是JAX WebService参考实现.相对于Axis2和CXF,JAXWS-RI是一个轻量级的框架.虽然是个轻量级框架,JAXWS-RI也提供了在Web服务器中发布Webservice的功能.官网地址https://jax-ws.jav

[转]Tomcat中的Session小结

阅读目录 什么是Session Session的目的 实现机制 Tomcat中的session实现 session存在的问题 什么是Session 对Tomcat而言,Session是一块在服务器开辟的内存空间,其存储结构为ConcurrentHashMap: Session的目的 Http协议是一种无状态协议,即每次服务端接收到客户端的请求时,都是一个全新的请求,服务器并不知道客户端的历史请求记录: Session的主要目的就是为了弥补Http的无状态特性.简单的说,就是服务器可以利用sess

Tomcat中JSP引擎工作原理

http://blog.csdn.net/linjiaxingqqqq/article/details/7164449 JSP运行环境: 执行JSP代码需要在服务器上安装JSP引擎,比较常见的引擎有WebLogic和Tomcat.把这些支持JSP的web服务器配置好后.就可以再客户端通过浏览器来访问JSP页面了.默认端口一般是7001. JSP生命周期: JSP处理请求的方法就是把这些请求都统一看做Servlet.由于这个原因,JSP的很多功能和生命周期,都由Java Servlet技术标准定义

【Mail】Tomcat提供JNDI方式支持JavaMail(三)

流程介绍 Tomcat提供了JavaMail的支持,是通过JNDI的方式实现的,具体流程是: Tomcat启动的时候,自身产生一个Session对象,放在JNDI容器中给其他项目调用,其他项目只要通过JNDI API就能获取JNDI中的对象,并在项目中使用. 使用步骤 配置JNDI资源:新建一个javaweb的maven项目,并在webapp/META-INF中新建一个文件context.xml. context.xml文件内容如下: 1 <Context> 2 <Resource na

spring boot + jersey工程由jar包转为war包在tomcat中启动报错问题

第一步: 在maven下,将Spring Boot工程由jar转换为war包启动,很简单,将pom.xml文件中的packaging改为war <packaging>war</packaging> 如果你使用Gradle,你需要修改build.gradle来将war插件应用到项目上: apply plugin: 'war'第二步: 产生一个可部署war包的第一步是提供一个SpringBootServletInitializer子类,并覆盖它的configure方法.这充分利用了Sp

使用 CAS 在 Tomcat 中实现单点登录

单点登录(Single Sign On , 简称 SSO )是目前比较流行的服务于企业业务整合的解决方案之一, SSO 使得在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统.CAS(Central Authentication Service)是一款不错的针对 Web 应用的单点登录框架,本文介绍了 CAS 的原理.协议.在 Tomcat 中的配置和使用,对于采用 CAS 实现轻量级单点登录解决方案的入门读者具有一定指导作用. CAS 介绍 CAS 是 Yale 大学发起的一

CAS 在 Tomcat 中实现单点登录

单点登录(Single Sign On , 简称 SSO )是目前比较流行的服务于企业业务整合的解决方案之一, SSO 使得在多个应用系统 中,用户只需要登录一次就可以访问所有相互信任的应用系统.CAS(Central Authentication Service)是一款不错的针 对 Web 应用的单点登录框架,本文介绍了 CAS 的原理.协议.在 Tomcat 中的配置和使用,对于采用 CAS 实现轻量级单点登录解决方案 的入门读者具有一定指导作用. CAS 介绍 CAS 是 Yale 大学发

tomcat中Servlet的工作机制

在研究Servlet在tomcat中的工作机制前必须先看看Servlet规范的一些重要的相关规定,规范提供了一个Servlet接口,接口中包含的重要方法是init.service.destroy等方法,Servlet在初始化时要调用init方法,在销毁时要调用destroy方法,而对客户端请求处理时则调用service方法.对于这些机制的支持都必须由Tomcat内部去支持,具体则是由Wrapper容器提供支持. 在tomcat中消息流的流转机制是通过四个不同级别的容器管道机制进行流转的,对于每个