Servlet 3.0中的一些主要提升点:
- 异步支持
- 容易配置
- 可插拔性
- 对已有API的改进
本文的目的是回答这个问题:为何要异步?
Web 2.0技术极大地改变了Web客户端和服务端之间的通信状况。Servlet 3.0中的异步支持被设计来应对这个新的挑战。为了让大家理解异步处理的重要性,让我们先来看看HTTP协议的演变。
-
HTTP协议的演变
从HTTP 1.0到HTTP 1.1,最终的一个改变就是持久化连接(connection:keep-alive)。HTTP 1.0中,一个Web客户端和服务端之间的连接在单次request/response循环后就被关闭了。在HTTP 1.1中,连接为多次请求一直保持活跃并且被重用。持久连接明显降低了通信延迟,因为客户端不需要在每个请求之后重新建立TCP连接。
下面是三种场景,从中可以体会到为什么需要异步处理。
- 每个连接一个线程(Thread per connection)
对于提供商来说,怎样使Web服务器更加具有可扩展性是一个挑战。基于HTTP 1.1的持久连接,每个HTTP连接一个线程模型是提供商采用的一个普遍方法。对于这种策略,在客户端和服务端之间的每一个HTTP连接在服务端都会关联一个线程。线程从服务器管理的线程池中分配。一旦连接被关闭,关联的线程被回收到线程池并且准备服务其它任务。根据硬件配置,这种方式可以扩展到一个很大数量的并发连接。在主流Web服务器试验中得出的数据显示,内存消耗的增长比例与HTTP连接的数量成正比。原因是线程消耗内存相对较大。服务端配置一个固定大小的线程可能遭遇线程饥饿问题,而且一旦线程池里所有线程都被占有,新客户端的请求会被拒绝。
另一方便,对于很多Web站点,用户仅是偶发性地从服务端请求页面。这是众所周知的page-by-page模型。连接线程在大多数时候都是空闲的,这浪费了大量资源。 - 每个请求一个线程(Thread per connection)
由于在Java 4的New I/O APIs中引入了非阻塞I/O能力,一个持久HTTP连接不要求一个线程一直分配给这个连接。仅当请求正在被处理时线程才能够被分配给连接。当一个连接在请求之间是空闲的,线程能够被回收,并且连接被放在一个集中NIO选择集合中来检测新的请求,而无需消耗一个单独的线程。这种模型称之为每个请求一个线程,这允许Web服务器使用一个固定数量的线程来处理不断增加的用户连接。在相同的硬件配置中,Web服务器在这种模型下比每个连接一个线程更具扩展性。当下流行的Web服务器,包括Tomcat、Jetty、GlassFish、WebLogic和WebSphere,它们都通过Java NIO使用每个请求一个线程模型。对于应用开发者而言,好消息是Web服务器以隐藏的方式实现了non-blocking I/O,没有通过servlet APIs暴露给任何应用。 - 应对Ajax挑战
为了用更加响应的接口来提供更好的用户体验,越来越多的Web应用使用Ajax。Ajax应用的用户与Web服务器交互比page-by-page模型更加频繁。不像普通的用户请求,Ajax请求能够被一个客户端频繁地发送给服务器。此外,客户端和运行在客户端的脚本能够定期轮训Web服务器来获取更新。更多的并发请求会引起更多线程被消耗,这抵消了thread-per-request方式带来的好处。 - Slowing running,limited resources
一些slow-running后端程序使这种状况更加糟糕。比如,一个请求能够被一个已经耗尽的JDBC连接池,或者一个低吞吐Web服务终端阻塞。长时间地挂起请求可能让线程被卡主,直到资源变为可用。更好的方式是把请求放到一个等待可用资源的中心队列中,并且回收那个线程。这有效地调整了请求线程的数量来匹配运行缓慢终端程序的能力。这也暗示了,在请求处理的某个点(当请求被存储在一个队列中),没有线程为了请求而被消耗。Servlet 3.0中的异步支持被设计通过一个通用和可移植的方法来实现这个方案,无论Ajax是否被使用。
参考资料
- JAVA WORLD
- Servlet 3.1异步
- Servlet 3.0规范