使用 Java NIO 编写高性能的服务器
从 JDK 1.4 开始,Java 的标准库中就包含了 NIO,即所谓的“New IO”。其中最
重要的功能就是提供了“非阻塞”的 IO,当然包括了 Socket。NonBlocking 的 IO 就
是对 select(Unix 平台下)以及 WaitForMultipleObjects(Windows 平台)的封装,提供
了高性能、易伸缩的服务架构。
说来惭愧,直到 JDK1.4 才有这种功能,但迟到者不一定没有螃蟹吃,NIO 就
提供了优秀的面向对象的解决方案,可以很方便地编写高性能的服务器。
话说回来,传统的 Server/Client 实现是基于 Thread per request,即服务器为
每个客户端请求建立一个线程处理,单独负责处理一个客户的请求。比如像 Tomcat
(新版本也会提供 NIO 方案)、Resin 等 Web 服务器就是这样实现的。当然为了
减少瞬间峰值问题,服务器一般都使用线程池,规定了同时并发的最大数量,避免
了线程的无限增长。
但这样有一个问题:如果线程池的大小为 100,当有 100 个用户同时通过 HTTP
现在一个大文件时,服务器的线程池会用完,因为所有的线程都在传输大文件了,
即使第 101 个请求者仅仅请求一个只有 10 字节的页面,服务器也无法响应了,只
有等到线程池中有空闲的线程出现。
另外,线程的开销也是很大的,特别是达到了一个临界值后,性能会显著下降,
这也限制了传统的 Socket 方案无法应对并发量大的场合,而“非阻塞”的 IO 就能轻
松解决这个问题。
下面只是一个简单的例子:服务器提供了下载大型文件的功能,客户端连接上
服务器的 12345 端口后,就可以读取服务器发送的文件内容信息了。注意这里的服
务器只有一个主线程,没有其他任何派生线程,让我们看看 NIO 是如何用一个线程
处理 N 个请求的。
NIO 服务器最核心的一点就是反应器模式:当有感兴趣的事件发生的,就通知
对应的事件处理器去处理这个事件,如果没有,则不处理。所以使用一个线程做轮
询就可以了。当然这里这是个例子,如果要获得更高性能,可以使用少量的线程,
一个负责接收请求,其他的负责处理请求,特别是对于多 CPU 时效率会更高。
UnRegistered