高性能Tomcat:漫谈行走在sendfile之上的Tomcat

jopen 9年前

原文出处: 陆晨

Tomcat常用的三种IO模式

提起高性能JAVA IO大家一定会想到NIO,AIO等等,然而我们的Tomcat是java写的一个优秀的开源的web服务,它与NIO有什么关系呢?我们的tomcat应用是不是早就跑在nio之上了呢?

答案是Tomcat是支持NIO的,到Tomcat7为止,默认的Tomcat包里面的配置并没有开启NIO连接器。下面我先简单介绍一下Tomcat的三种IO策略:BIO,NIO,APR。

BIO:大家是否记得我们初学网络编程的时候,我们写的第一个服务器程序……,BIO是Tomcat默认开启的IO模式,性能灰常底下,没有经过任何的优化处理。

NIO:利用JAVA的非阻塞IO技术,开启这个模式非常简单,只需要把tomcat子目录conf下面的server.xml修改一下即可,找到下面,将protocol改为org.apache.coyote.http11.Http11NioProtocol即可。

<Connector port="8080" protocol="HTTP/1.1"                 connectionTimeout="20000"                 redirectPort="8443" />
上面是默认的配置,我们改成下面这个样子:
<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"                 connectionTimeout="20000"                 redirectPort="8443" />

这样重启你的tomcat就好,有没有被easy到?

APR:这种模式使用起来最优门槛,除非要将上面的配置改成下面这样:

<Connector port="8080" protocol="org.apache.coyote.http11.Http11AprProtocol"                 connectionTimeout="20000"                 redirectPort="8443" />

还需要下载APR模式需要的库,因为这种模式是基于JNI的,即JAVA调用本地库的方式来进行IO。

Tomcat对静态资源的处理

大家都知道,Tomcat为我们提供了一个专门用来处理静态资源的Servler,它叫DefaultServlet,看看这个Servlet的源代码,其中对资源处理方法的实现里面有下面这样一段:

if (ostream != null) {                      if (!checkSendfile(request, response, cacheEntry, contentLength, null))                          copy(cacheEntry, renderResult, ostream);                  } else {                      copy(cacheEntry, renderResult, writer);                  }

作者的意思是这里判断如果没有满足使用sendfile条件的情况下我们就直接将静态资源数据读取,写入response的输出流,那么如果满足使用sendfile条件,这里是不做处理的,这里会把资源文件的大小即名称放在request的属性集合里面带给底层,让底层来处理。

Tomcat定义了三种类型的Endpoint,他们分别是BIO,NIO及APR三种模式下底层网络处理的具体实现,比如NioEndpoint里面调用了FileChannel的transferTo方法,transferTo会利用操作系统的sendfile系统调用来将磁盘文件输出到网络。同样AprEndpoint最为哦APR方式的实现会调用Native来实现IO。

send和sendfile

Tomcat中的NIO模式和APR模式对静态文件处理的高效根源在于地从对不同的系统调用,和普通的文件传输不同的是NIO和APR方式都使用的操作系统的sendfile系统调用来对文件进行IO,而普通的方式是使用了read和write系统调用。name这两种类型有什么不同呢,这是本文的关键点:

 高性能Tomcat:漫谈行走在sendfile之上的Tomcat

文件读取以发送至网络流程图

先看看上面这张图,使用read和write方式的时候,将文件输出到网络的流程是这样的:

1,read操作先将线程从用户态切换到内核态,将文件从磁盘读到内核缓冲器。

2,read将文件从内核缓冲区读到用户地址空间,同时线程从内核态切换到用户态。

3,read返回。

4,对文件进行处理

5,write将线程从用户态切换到内核态,将文件写到操作系统内核网络部分的缓冲区。

6,write将线程切换到用户态并返回。

上面的的过程涉及到四次操作系统内核态与用户态的切换,代价是昂贵的。由于事实上我们并不需要对静态文件进行处理这个步骤,为什么要绕一个圈子呢从内核copy到用户态又copy到内核态呢,因此sendfile来了。

我们再来看看sendfile的处理流程:

1,将文件读到操作系统的内核缓冲区。

2,将文件copy到操作系统跟网络相关的内核缓冲区。

上面不会涉及到内核态到用户态以及用户态到内核态的切换,sendfile是linux2+version提供的系统调用,而且在linux2.4+version版本之后提供能zero-copy特性,上面这些说明了sendfile为我们的程序提高了问静态文件处理的能力和性能。