一聚教程网:一个值得你收藏的教程网站

热门教程

Tomcat 的异步 Servlet 实现例子与原理

时间:2022-06-29 02:34:59 编辑:袖梨 来源:一聚教程网


了解Servlet规范的朋友应该都知道,从3.0开始,Tomcat的Servlet支持异步请求,或者说是Tomcat提供了异步Servlet,从而可以将一些耗时的操作放到独立的线程中,在 操作完成后再返回数据,不阻塞请求的执行和返回 ,甚至可以基于此 实现服务器推 的功能。

正好不久前,网友问了异步Servlet的实现原理的问题,


我把这块梳理了一下,一起来深入了解下。

首先,先从使用异步Servlet的方式来说。

我们的使用步骤大致是:

声明Servlet,注意增加asyncSupported的属性,开启异步处理支持。@WebServlet(urlPatterns = "/demo", asyncSupported = true)

在Servlet内部,需要独立线程处理的地方,使用request获取异步Context。 AsyncContext ctx = req. startAsync ();

在独立线程中,使用异步Context,可以获取到其绑定的request和response,此时,就可以按照原来Servlet的写法,继续编写逻辑了。

独立线程内的操作处理完成后,需要调用异步Context的complet方法,来结束该异步线程。

需要注意 的是,异步Servlet有对应的超时时间,如果在指定的时间内没有执行完操作,response依然会走原来Servlet的结束逻辑,后续的异步操作执行完再写回的时候,可能会遇到异常。

上面是使用异步Servlet的几个主要步骤,那这个背后,是如何实现的呢?下面我们来看原理。

我们在通过Request来获取异步的Context,这一步背后发生了什么呢?

public AsyncContext startAsync() {

return startAsync(getRequest(),response.getResponse()) ;

}

public AsyncContext startAsync(ServletRequest request,

ServletResponse response) {

if (! isAsyncSupported ()) { // 注意1

throw new IllegalStateException(sm.getString("request.asyncNotSupported"));

}

if (asyncContext == null) {

asyncContext = new AsyncContextImpl (this);

}

asyncContext.setStarted(getContext(), request, response,

request==getRequest() && response==getResponse().getResponse());

asyncContext. setTimeout (getConnector().getAsyncTimeout());// 注意2

return asyncContext;

}

我们上面的注意1,就是前面步骤里提到的,配置注解的时候,要开启异步处理支持,否则在这一步直接就会抛异常。

注意2,就是前面提到的异步超时设置。

我们看到整个方法是通过当前的request和response,生成了一个对应的AsyncContext,并进行相应的配置。

在setStart方法中,执行的主要逻辑有以下几点:

public void setStarted(Context context, ServletRequest request,

ServletResponse response, boolean originalRequestResponse) {

this.request. getCoyoteRequest().action(

                ActionCode. ASYNC_START , this) ; // 注意3

List listenersCopy = new ArrayList<>();

listenersCopy.addAll(listeners);

listeners.clear();

for (AsyncListenerWrapper listener : listenersCopy) {

try {

listener.fireOnStartAsync(event); // 注意4

} catch (Throwable t) {

}

}

}

上面注意3的地方,在Tomcat内部,许多的响应状态通知,都是通过类似的方式,以ActionCode的方式返回的,包括前面说的complete的方法调用等。

注意4,是关于异步的Context,可以添加许多的异步Listener,在特定的事件发生时,通知到Listener。

在Servlet规范中,对 startAsync 方法做了这样的描述:

A call to this method ensures that the response isn't committed when the application exits out of the service method. It is committed when AsyncContext.complete is called on the returned AsyncContext or the AsyncContext times out and there are no listeners associated to handle the time out. The timer for async time outs will not start until the request and it’s associated response have returned from the container. The AsyncContext could be used to write to the response from the async thread. It can also be used to just notify that the response is not closed and committed.

以上说明,可以帮助我们理解一部分异步Servlet的实现原理,我们从源码中来梳理具体的逻辑。

我们前面的文章分析过,整个请求的处理,到Container内各组件的调用,是从EndPoint到CoyoteAdaptor。其中,在Endpoint的代码中,调用是从这一行开始:

getAdapter().service(request, response);

而在此之后,整个service执行完成时,Adaptor中,会根据请求类型判断,对于非异步和comet的请求,会进行request和response的 finished 的操作,同时会进行 recycle 的操作。

AsyncContextImpl asyncConImpl = (AsyncContextImpl)request.getAsyncContext();

if ( asyncConImpl != null) {

async = true;

} else if (!comet) {

request.finishRequest();

response. finishResponse ();}

注意,在finishResponse的时候,outputBuffer. close(); 就会执行,recycle的时候,也是根据请求类型进行限制。注意CoyoteAdaptor中,会判断具体的请求种类,是否为comet或者async,

if (!comet && !async || error.get()) {

request.recycle();

response. recycle ();

此时,OutputBuffer就会被重置,所对于普通Servlet,以后面的其它操作就不能被继续写回了。
此外,Processor会判断请求的类型,从而决定是否进行特定的操作,比如

if (!i sAsync () && ! comet ) {

endRequest();

}

我们看到,在非异步请求并且也不是comet请求时,会执行endRequest操作。而具体返回Socket状态的时候,也是根据请求类型进行判断,对于异步的请求,返回的Socket状态为 LONG ,

else if ( isAsync () || comet) {

return SocketState.LONG;

Protocol类中,判断类型为LONG时,会进行如下操作:

if (state == SocketState.LONG) {

// In the middle of processing a request/response. Keep the

// socket associated with the processor. Exact requirements

// depend on type of long poll

connections.put(socket, processor);

longPoll(wrapper, processor);// 配置processor的属性

从而请求可以使用原处理器进行处理。

至于异步Context执行完成后的complete操作,主要是返回一个complete的ActionCode,在Processor中据此判断请求执行完成,设置SocketStatus为OPEN_READ,开始下一轮请求的接收。

public void complete() {

check();

request.getCoyoteRequest().action(ActionCode. ASYNC_COMPLETE , null);

}

case ASYNC_COMPLETE: {

socketWrapper.clearDispatches();

if (asyncStateMachine.asyncComplete()) {

endpoint.processSocket(this.socketWrapper, SocketStatus.OPEN_READ, true);

}

break;

}

以上,即为异步Servlet的基本实现原理。总结起来主要有:

主线请求执行完后Socket的longPool

response的状态未设置为finished,此时OutputBuffer未close,依然可以写回。

热门栏目