Servlet异步处理性能优化的过程
3.0版本之前
是Thread-Pre-Request模式即每一次Http请求都由某一个线程从头到尾负责处理
如果一个请求需要进行IO操作,比如访问数据库、调用第三方服务接口等,那么其所对应的线程将同步地等待IO操作完成, 而IO操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,在并发量越来越大的情况下,这将带来严重的性能问题
举例说明
从HttpServletRequest对象中获得一个AsyncContext对象,该对象构成了异步处理的上下文,Request和Response对象都可从中获取。AsyncContext可以从当前线程传给另外的线程,并在新的线程中完成对请求的处理并返回结果给客户端,初始线程便可以还回给容器线程池以处理更多的请求
举例说明
- 老的线程执行start方法
- start方法向servlet容器申请获取新的线程
- 新的线程执行业务逻辑
- 原先的那个老的线程也会被servelt容器的线程池回收
这种方式对性能的改进不大,因为如果新的线程和初始线程共享同一个线程池的话,相当于闲置下了一个线程,但同时又占用了另一个线程
手动创建线程
手动创建的线程,不能复用
因此,一种更好的办法是我们自己维护一个线程池。这个线程池不同于Servlet容器的主线程池
用户发起的请求首先交由Servlet容器主线程池中的线程处理,在该线程中,获取到AsyncContext,然后将其交给异步处理线程池
可以通过Java提供的Executor框架来创建线程池
3.1版本
Servlet 3.0对请求的处理虽然是异步的,但是对InputStream和OutputStream的IO操作却依然是阻塞的,对于数据量大的请求体或者返回体,阻塞IO也将导致不必要的等待。因此在Servlet 3.1中引入了非阻塞IO
通过在HttpServletRequest和HttpServletResponse中分别添加ReadListener和WriterListener方式,只有在IO数据满足一定条件时(比如数据准备好时),才进行后续的操作
上述的第2步 只有在完全读到请求数据的时候才会去申请业务线程
上述第5步 只有在完全准备好响应数据的时候才会去触发响应请求
举例
为ServletInputStream添加了一个ReadListener,并在ReadListener的onAllDataRead()方法中完成了长时处理过程