Servlet 3.0笔记之异步请求相关方法和AsyncContext
1、传统Servlet处理
Web容器会为每个请求分配一个线程,默认情况下,响应完成前,该线程占用的资源都不会被释放。若有些请求需要长时间(例如长处理时间运算、等待某个资源),就会长时间占用线程所需资源,若这类请求很多,许多线程资源都被长时间占用,会对系统的性能造成负担。
2、新特性:异步处理
Servlet 3.0新增了异步处理,可以先释放容器分配给请求的线程与相关资源,减轻系统负担,原先释放了容器所分配线程的请求,其响应将被延后,可以在处理完成(例如长时间运算完成、所需资源已获得)时再对客户端进行响应。
Servlet 3.0 之前,一个普通 Servlet 的主要工作流程大致如下:
第一步,Servlet 接收到请求之后,可能需要对请求携带的数据进行一些预处理;
第二步,调用业务接口的某些方法,以完成业务处理;
第三步,根据处理的结果提交响应,Servlet 线程结束。
其中第二步的业务处理通常是最耗时的,这主要体现在数据库操作,以及其它的跨网络调用等,在此过程中,Servlet 线程一直处于阻塞状态,直到业务方法执行完毕。在处理业务的过程中,Servlet 资源一直被占用而得不到释放,对于并发较大的应用,这有可能造成性能的瓶颈。对此,在以前通常是采用私有解决方案来提前结束 Servlet 线程,并及时释放资源。
Servlet 3.0 针对这个问题做了开创性的工作,现在通过使用 Servlet 3.0 的异步处理支持,之前的 Servlet 处理流程可以调整为如下的过程:
第一步,Servlet 接收到请求之后,可能首先需要对请求携带的数据进行一些预处理;
第二步,Servlet 线程将请求转交给一个异步线程来执行业务处理,线程本身返回至容器,
第三步,Servlet 还没有生成响应数据,异步线程处理完业务以后,可以直接生成响应数据(异步线程拥有 ServletRequest 和 ServletResponse 对象的引用),或者将请求继续转发给其它 Servlet。
Servlet 线程不再是一直处于阻塞状态以等待业务逻辑的处理,而是启动异步线程之后可以立即返回。
AsyncContext源码
@Override
public AsyncContext startAsync() {
return startAsync(getRequest(),response.getResponse());
}
@Override
public AsyncContext startAsync(ServletRequest request,
ServletResponse response) {
if (!isAsyncSupported()) {
IllegalStateException ise =
new IllegalStateException(sm.getString("request.asyncNotSupported"));
log.warn(sm.getString("coyoteRequest.noAsync",
StringUtils.join(getNonAsyncClassNames())), ise);
throw ise;
}
if (asyncContext == null) {
asyncContext = new AsyncContextImpl(this);
}
asyncContext.setStarted(getContext(), request, response,
request==getRequest() && response==getResponse().getResponse());
asyncContext.setTimeout(getConnector().getAsyncTimeout());
return asyncContext;
}
AsyncContext应用demo
使用场景
该方法主要是将客户端的长轮询请求添加到某个东西中去:服务端将客户端的长轮询请求封装成一个叫 ClientLongPolling 的任务,交给 scheduler 去执行,但是request,和response不会立即返回客户端直到asyncContext.complete才返回,generateResponse是nacos客户端长轮询请求达到超时时间30秒或者检测到配置变更才会调用
api说明
在ServletRequest中增加的有关异步相关方法分为:
startAsync(servletRequest, servletResponse) 传入指定的request和response对象,便于在AsyncContext中重复使用(这样被Filter、Servlet包装过的请求、相应对象才会在异步的环境下继续生效)。
startAsync() 若request或者response被包装,将失去预期的效果。
isAsyncSupported() 和 isAsyncStarted()
辅助性的方法,用于判断当前请求是否支持异步或者异步已经开始。
getAsyncContext()
需要在异步启动之后才能够访问,否则会报异常。
在AsyncContext中分发的方法有三个,不太容易区分:
AsyncContext.dispatch()
若当前AsyncContext由ServletRequest.startAsync(ServletRequest, ServletResponse)方法启动,返回的地址可以通过HttpServletRequest.getRequestURI()得到。
否则,分发的地址则是当前URL request对象最后一次分发的地址。
虽有些拗口,两者分发的地址大部分情况下一致;但尽量使用带有参数的异步上下文启动器。
如本例中请求/asyncDispatch2Async?disUrl=self,执行dispatch()方法之后,自身会再次分发到自身,包括传递的参数。
AsyncContext.dispatch(String path)
等同于ServletRequest.getRequestDispatcher(String),算是一个快捷方法。
可以转向一个同步或异步的servlet,或者JSP,或其它资源地址等。
AsyncContext.dispatch(ServletContext context, String path)
请求的地址将在给定的上下文里面(ServletContext),有可能传入的上下文与当前站带你应用的上下文有所区别
总结
容器里边实现 request 的时候尽量复用,而不是收回,但是组件的异步处理功用被启用(request 上调用了 startAsync 办法)。request 的生命周期在遇到异步的时候有点特殊,仅仅会释放容器分配给请求的线程与相关资源但是request和response不会被释放可以在子线程获取,待子线程耗时操作执行完才会一起释放返回客户端