Apache Tomcat 8 中的 NIO 2

jopen 11年前

Apache Tomcat 8 有一个新的基于 NIO 2 的连接器正接近正常的使用状态,而现在被标记成测试版. NIO 2 不仅向 Servlet 3.1的异步IO看齐,它还不止这一个好处.

速度

首先,是一个快速的速度测试. 原生的速度使用一个Servlet写1KB的数据来衡量, 使用 ab -k -c 100 (启用超过100个并发连接并保持存活) 以让其只去测量做一次快的写入和在两个请求之前保持连接. 明显这是一个可怕的环境标准,但是这个主意只是要看看 NIO 2 是不是足够快, 因为在你注意到它的高级API的时候,它看起来是有点慢的. 这可能会消除NIO 2作为一个很有用的解决方案在你心目中的存在感,因为Tomcat中已经存在一个稳定的NIO连接器了, 不过在可选范围的另外一端,APR是接近原生速度的. 我很高兴的像大家报告 NIO 2 显著地比 NIO 在这种纯块/轮询的压力测试上要快, 要快上大约50%, 并且相当于APR做这种任务的速度.

在这个关键问题有了结果之后,我们就有了一个比目前的连接器更优雅的选择, 因为NIO和APR的轮询管理,NIO的阻塞IO和APR的本地代码已经被证明存在看似无休止的复杂、思索、奔溃、平台特定等诸多问题.

不过,尽管一些初步的薄弱环节已经确定可以使用JSSE和静态文件服务(见下文)来解决,其在现实世界的好处和资源消耗怎么样现在还是个未知数. 随着线程和轮询管理被完全的抽象出来,JVM最终将会把一切掌控起来,已提供优化的行为.

一个简单的API

那么它是一个简单的API吗 ? 实际上,只有阻塞IO使用NIO 2来做才非常简单. 像使用NIO一样,一次读或者写会立即返回结果, 但是不同于 NIO 这种操作没必要是完整的,它还可以继续异步存在与进程中. 为了能有所显示,最基本的读/写API都使用到了一个可以在一旁被轮询(这是一个糟糕的点子)或者阻塞的Future对象.因此,简单的带有操作时效的阻塞,看起来不错.

"非阻塞"由于在Servlet 3.1中被引入,需要使用更加复杂的使用任务完成处理程序来通知操作现在已经完成的API. 那同样也是听起来很简单,但是有特殊的问题需要处理,因为NIO 2 API不会提供让处理那种问题更简单的所有东西. 一次调用可以完全是(很明显也可以不是)完全内联的, 同步是不直观的 (当一个操作被挂起是,没有代码块会同步上, 不过看样子一些像buffer这样的重要的对象的状态没有被定义;死锁的风险也还存在), 不完整的操作是可能的,等等.

API 确实对一些更加重要的IO进行了优化,使用分散和集中. 我尝试去利用Tomcat中后者的优势,未来可以在其上做更多的工作.

为什么NIO 2 会更好

NIO 2看起来简单,快速且直观, 但它内部的一些东西仍有待改进.

发送文件的支持

NIO 的transferTo API并不被NIO 2 异步通道所支持, 并且我不认为这样是明智的. 因此, 尽管NIO 2连接器的原始速度不慢, 并且在大多数情况下它也足够快速, 但它仍不是最高效的文件服务器. 虽然无关紧要,但因为实现起来也不太费事, 所以这是个不幸.

JSSE集成

与使用SSL引擎的API类似, 通过NIO可以提供良好控制和非阻塞能力. 但所有人都要写相似的异步通道封装代码. 而JSSE通道代码已经被NIO 2包括了.

JSSE (无) 速度

与OpenSSL相比, JSSE仍跟以前一样慢. 虽然到目前为止你已经对这伤心事有了免疫力, JSSE 现在仍看起来是在浪费服务器资源. 然而,这个 JVM组件是可拨插的, 所以我们看以后是否会有所改善.

更好的状态控制

当使用completion handler时没法做像查询操作状态这样的基本的事, 除非使用Future. 待定标识可以在其他地方获得, 并且实际上这个标识是一个与future(能够等候待定操作完成)共享的int类型的信号灯. 最后, 虽然这看起来挺直观也没有什么困难的, 但它会导致比需要的更为复杂.

所以NIO 2仍有改进的空间. 下面, 期待它的发生!