17.12 异步处理

7次阅读

共计 2943 个字符,预计需要花费 8 分钟才能阅读完成。

应用服务器的 web 容器通常会为每个请求分配一个服务线程。在重负载的场景下,容器需要大量的线程去服务所有客户端请求。服务可扩展性的限制包括内存不足或者耗尽容器线程。创建可扩展 web 程序,你必须确保没有关联请求的线程是空闲的,所以容器可以使用他们处理新请求。
这里有两个关联请求的线程空闲的两个场景:

  • 线程需要在构建响应之前等待一个资源可用或者处理数据。例如,一个应用程序需要在构建响应前查询数据库或者通过远程 web 服务访问数据。
  • 线程需要在构建响应之前等待一个事件。例如,线程在构建响应之前需要等待一个 JMS 消息,另一个客户端的新信息,或者队列里面的新数据可用。

这些场景代表限制 web 程序可扩展性的阻塞操作。异步处理是指给这些阻塞操作分配一个新线程,并把关联请求处理的线程返回给 web 容器。

servlet 异步处理

java ee 支持 servlet 和 filter 的异步处理。如果一个 servlet 或一个 filter 处理请求时可能到达一个阻塞操作,它可以把操作分配给一个异步处理上下文并且在不生成响应的情况下将关联请求处理的线程回送给 web 容器。阻塞操作在不同线程的异步上下文中执行完成,它可以生成响应或者转发请求到另一个 servlet。
在一个 servlet 上启用异步处理,设置 @WebServlet 注解的 asyncSupported 参数为 true,如下:

@WebServlet(urlPatterns={"/asyncservlet"}, asyncSupported=true)
public class AsyncServlet extends HttpServlet {...}

javax.servlet.AsyncContext 类提供在 Service 方法中执行异步处理所需的功能。获得一个 AsyncContext 实例,在 service 方法中调用 request 对象的 startAsync() 方法;例如:

public void doGet(HttpServletRequest req, HttpServletResponse resp) {
   ...
   AsyncContext acontext = req.startAsync();
   ...
}

这个调用将请求进入异步模式并且确保响应在退出 service 方法时每月被提交。你必须在异步上下文完成阻塞操作时生成响应或者转发到其他 servlet 中。
AsyncContext 类提供的基础功能描述:
方法签名:void start(Runnable run)
描述:容器提供的能提供阻塞操作处理的不同线程
方法签名:ServletRequest getRequest()
描述:返回用来初始化异步上下文的请求。在上面的例子中,request 与 service 方法中的相同。你可以通过这个方法在异步上下文中从请求中获取参数。
方法签名:ServletResponse getResponse()
描述:返回初始化异步上下文的响应。在上面的例子中,response 与 service 方法中的相同。你可以在异步上下文中,使用这个方法写入阻塞操作的结果到响应中。
方法签名:void complete()
描述:完成异步操作,并关闭与此异步上下文关联的响应。你可以在异步上下文完成写入响应后调用这个操作。
方法签名:void dispatch(String path)
描述:转发请求和响应到给定的路径。在阻塞操作完成后,使用这个方法调用另一个 servlet 写出响应。

等待一个资源

这个章节示范了怎么使用 AsyncContext 上下文提供的功能,有如下用例:

  1. servlet 从一个 GET 请求中获取参数
  2. servlet 使用一个资源,比如一个数据库或者一个 web service,基于这个参数获取信息。这个资源可能比较缓慢,所以这可能是一个阻塞操作。
  3. servlet 使用资源的结果生成响应。

下面的例子是一个不使用异步处理的常见 servlet:

@WebServlet(urlPatterns={"/syncservlet"})
public class SyncServlet extends HttpServlet {
   private MyRemoteResource resource;
   @Override
   public void init(ServletConfig config) {resource = MyRemoteResource.create("config1=x,config2=y");
   }

   @Override
   public void doGet(HttpServletRequest request, 
                     HttpServletResponse response) {response.setContentType("text/html;charset=UTF-8");
      String param = request.getParameter("param");
      String result = resource.process(param);
      /* ... print to the response ... */
   }
}

下面的示例是同一个 servlet,但使用了异步处理:

@WebServlet(urlPatterns={"/asyncservlet"}, asyncSupported=true)
public class AsyncServlet extends HttpServlet {
   /* ... Same variables and init method as in SyncServlet ... */

   @Override
   public void doGet(HttpServletRequest request, 
                     HttpServletResponse response) {response.setContentType("text/html;charset=UTF-8");
      final AsyncContext acontext = request.startAsync();
      acontext.start(new Runnable() {public void run() {String param = acontext.getRequest().getParameter("param");
            String result = resource.process(param);
            HttpServletResponse response = acontext.getResponse();
            /* ... print to the response ... */
            acontext.complete();}
}

AsyncServlet 在 @WebServlet 注解属性中添加 asyncSupported=true。其余的差异在 service 方法中:

  • request.startAsync() 引发 request 被异步执行;response 不会在 service 方法结束时发送到客户端;
  • acontext.start(new Runnable() {…}) 从容器中得到一个新的线程。
  • 内部类中的 run() 中的代码在一个新线程中执行。内部类需要从异步上下文中读取请求参数和写入响应。调用异步上下文的 complete() 方法来提交并发送响应到客户端。

AsyncServlet 的 service 方法立即返回,同时请求在异步上下文中处理。

正文完
 0