浅谈servlet3异步原理与实践

一、什么是Servlet

servlet 是基于 Java 的 Web 组件,由容器进行管理,来生成动态内容。像其他基于 Java 的组件技术一样,servlet 也是基于平台无关的 Java 类格式,被编译为平台无关的字节码,可以被基于 Java 技术的 Web 服务器动态加载并运行。容器(Container),有时候也叫做 servlet 引擎,是 Web 服务器为支持 servlet 功能扩展的部分。客户端通过 servlet 容器实现的 request/response paradigm(请求/应答模式) 与 Servlet 进行交互。

二、什么是Servlet规范

每当一个Servlet版本发布都会对应一个Servlet版本的规范,比如Servlet2.5、Servlet3.0、Servlet3.1.
规范中描述了Java Servlet API 的标准,定义了 Java Servlet API 中类、接口、方法签名的完整规范且附带的Javadoc 文档供开发人员查阅,目的主要是为Java Servlet 给出一个完整和清晰的解释。从下图可以看出Servlet规范版本和tomcat支持的版本的对应关系。比如Servlet3是从tomcat7以后开始支持的。

Servlet和tomcat版本.png

三、同步,异步,阻塞,非阻塞

同步异步是数据通信的方式,阻塞和非阻塞是一种状态。比如同步这种数据通讯方式里面可以有阻塞状态也可以有非阻塞状态。

四、Servlet3的异步位置

这里说的位置是指,从tomcat处理整个request请求流程中,异步处于哪一步。我们先梳理出在NIO模式下(是否使用NIO跟异步没有直接关系,这里是拿NIO模式下的tomcat流程做说明),下面这个图是tomcat的总体结构,里面用箭头标明了请求线路。

tomcat架构图.png

我们知道在tomcat的组件中Connector和Engine是最核心的两个组件,Servlet3的异步处理就是发生在Connector中。Tomcat的组件之间的协作关系,后续会单独写一篇文章介绍。这里先有一个直观的认识。便与后续对异步理解。

五、Servlet3的异步流程

Servlet异步处理流程图.png

接收到request请求之后,由tomcat工作线程从HttpServletRequest中获得一个异步上下文AsyncContext对象,然后由tomcat工作线程把AsyncContext对象传递给业务处理线程,同时tomcat工作线程归还到工作线程池,这一步就是异步开始。在业务处理线程中完成业务逻辑的处理,生成response返回给客户端。在Servlet3.0中虽然处理请求可以实现异步,但是InputStream和OutputStream的IO操作还是阻塞的,当数据量大的request body 或者 response body的时候,就会导致不必要的等待。从Servlet3.1以后增加了非阻塞IO,需要tomcat8.x支持。

六、Servlet3的异步使用步骤

我们使用的大致步骤如下:

1、声明Servlet,增加asyncSupported属性,开启异步支持。@WebServlet(urlPatterns = "/AsyncLongRunningServlet", asyncSupported = true)
2、通过request获取异步上下文AsyncContext。AsyncContext asyncCtx = request.startAsync();
3、开启业务逻辑处理线程,并将AsyncContext 传递给业务线程。executor.execute(new AsyncRequestProcessor(asyncCtx, secs));
4、在异步业务逻辑处理线程中,通过asyncContext获取request和response,处理对应的业务。
5、业务逻辑处理线程处理完成逻辑之后,调用AsyncContext 的complete方法。asyncContext.complete();从而结束该次异步线程处理。

七、Servlet3的异步使用示例

7.1、AsyncLongRunningServlet.java 处理Servlet请求,并开启异步

package com.test.servlet3;

import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * Created by wangxindong on 2017/10/19.
 */
@WebServlet(urlPatterns = "/AsyncLongRunningServlet", asyncSupported = true)
public class AsyncLongRunningServlet extends HttpServlet {
  private static final long serialVersionUID = 1L;

  protected void doGet(HttpServletRequest request,
             HttpServletResponse response) throws ServletException, IOException {
    long startTime = System.currentTimeMillis();
    System.out.println("AsyncLongRunningServlet Start::Name="
        + Thread.currentThread().getName() + "::ID="
        + Thread.currentThread().getId());

    request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);

    String time = request.getParameter("time");
    int secs = Integer.valueOf(time);
    // max 10 seconds
    if (secs > 10000)
      secs = 10000;

    AsyncContext asyncCtx = request.startAsync();
    asyncCtx.addListener(new AppAsyncListener());
    asyncCtx.setTimeout(9000);//异步servlet的超时时间,异步Servlet有对应的超时时间,如果在指定的时间内没有执行完操作,response依然会走原来Servlet的结束逻辑,后续的异步操作执行完再写回的时候,可能会遇到异常。

    ThreadPoolExecutor executor = (ThreadPoolExecutor) request
        .getServletContext().getAttribute("executor");

    executor.execute(new AsyncRequestProcessor(asyncCtx, secs));
    long endTime = System.currentTimeMillis();
    System.out.println("AsyncLongRunningServlet End::Name="
        + Thread.currentThread().getName() + "::ID="
        + Thread.currentThread().getId() + "::Time Taken="
        + (endTime - startTime) + " ms.");
  }
}

7.2、AppAsyncListener.java 异步监听器

package com.test.servlet3;

import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebListener;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * Created by wangxindong on 2017/10/19.
 */
@WebListener
public class AppAsyncListener implements AsyncListener {
  @Override
  public void onComplete(AsyncEvent asyncEvent) throws IOException {
    System.out.println("AppAsyncListener onComplete");
    // we can do resource cleanup activity here
  }

  @Override
  public void onError(AsyncEvent asyncEvent) throws IOException {
    System.out.println("AppAsyncListener onError");
    //we can return error response to client
  }

  @Override
  public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
    System.out.println("AppAsyncListener onStartAsync");
    //we can log the event here
  }

  @Override
  public void onTimeout(AsyncEvent asyncEvent) throws IOException {
    System.out.println("AppAsyncListener onTimeout");
    //we can send appropriate response to client
    ServletResponse response = asyncEvent.getAsyncContext().getResponse();
    PrintWriter out = response.getWriter();
    out.write("TimeOut Error in Processing");
  }
}

7.3、AppContextListener.java Servlet上下文监听器,可以在里面初始化业务线程池

package com.test.servlet3;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Created by wangxindong on 2017/10/19.
 * 在监听中初始化线程池
 */
@WebListener
public class AppContextListener implements ServletContextListener {
  public void contextInitialized(ServletContextEvent servletContextEvent) {

    // create the thread pool
    ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L,
        TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(100));
    servletContextEvent.getServletContext().setAttribute("executor",
        executor);

  }

  public void contextDestroyed(ServletContextEvent servletContextEvent) {
    ThreadPoolExecutor executor = (ThreadPoolExecutor) servletContextEvent
        .getServletContext().getAttribute("executor");
    executor.shutdown();
  }
}

7.4、AsyncRequestProcessor.java 业务工作线程

package com.test.servlet3;

import javax.servlet.AsyncContext;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * Created by wangxindong on 2017/10/19.
 * 业务工作线程
 */
public class AsyncRequestProcessor implements Runnable {
  private AsyncContext asyncContext;
  private int secs;

  public AsyncRequestProcessor() {
  }

  public AsyncRequestProcessor(AsyncContext asyncCtx, int secs) {
    this.asyncContext = asyncCtx;
    this.secs = secs;
  }

  @Override
  public void run() {
    System.out.println("Async Supported? "
        + asyncContext.getRequest().isAsyncSupported());
    longProcessing(secs);
    try {
      PrintWriter out = asyncContext.getResponse().getWriter();
      out.write("Processing done for " + secs + " milliseconds!!");
    } catch (IOException e) {
      e.printStackTrace();
    }
    //complete the processing
    asyncContext.complete();
  }

  private void longProcessing(int secs) {
    // wait for given time before finishing
    try {
      Thread.sleep(secs);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

八、Tomcat NIO Connector ,Servlet 3.0 Async,Spring MVC Async的关系

对于这几个概念往往会混淆,这里做一个梳理比较,nio是一种IO的模型,对比与传统的BIO,它可以利用较少的线程处理更多的连接从而增加机器的吞吐量,Tomcat NIO Connector是Tomcat的一种NIO连接模式。异步,前面提到他是一种通讯的方式,它跟NIO没有任务关系,及时没有NIO也可以实现异步,Servlet 3.0 Async是指Servlet 3规范以后支持了异步处理Servlet请求,我们可以把请求线程和业务线程分开。Spring MVC Async是在Servlet3异步的基础上做了一层封装。具体的区别如下:

8.1、Tomcat NIO Connector

Tomcat的Connector 有三种模式,BIO,NIO,APR,Tomcat NIO Connector是其中的NIO模式,使得tomcat容器可以用较少的线程处理大量的连接请求,不再是传统的一请求一线程模式。Tomcat的server.xml配置protocol="org.apache.coyote.http11.Http11NioProtocol",Http11NioProtocol 从 tomcat 6.x 开始支持。NIO的细节可以参看NIO相关技术文章。

8.2、Servlet 3.0 Async

是说Servlet 3.0支持了业务请求的异步处理,Servlet3之前一个请求的处理流程,请求解析、READ BODY,RESPONSE BODY,以及其中的业务逻辑处理都由Tomcat线程池中的一个线程进行处理的。那么3.0以后我们可以让请求线程(IO线程)和业务处理线程分开,进而对业务进行线程池隔离。我们还可以根据业务重要性进行业务分级,然后再把线程池分级。还可以根据这些分级做其它操作比如监控和降级处理。servlet 3.0 从 tomcat 7.x 开始支持。

8.3、Spring MVC Async

是Spring MVC 3.2 以上版本基于Servlet 3的基础做的封装,原理及实现方式同上,使用方式如下:

@Controller
@RequestMapping("/async/TestController")
public class TestController {
  @ResponseBody
  @RequestMapping("/{testUrl}")
  public DeferredResult<ResponseEntity<String>> testProcess(@PathVariable String testUrl) {
    final DeferredResult<ResponseEntity<String>> deferredResult = new DeferredResult<ResponseEntity<String>>();

    // 业务逻辑异步处理,将处理结果 set 到 DeferredResult
    new Thread(new AsyncTask(deferredResult)).start();

    return deferredResult;
  }

  private static class AsyncTask implements Runnable {

    private DeferredResult result;

    private AsyncTask(DeferredResult result) {
      this.result = result;
    }

    @Override
    public void run() {
      //业务逻辑START
      //...
      //业务逻辑END
      result.setResult(result);
    }
  }
}

九、Servlet3非阻塞IO

Servlet3.1以后增加了非阻塞IO实现,需要Tomcat8.x以上支持。根据Servlet3.1规范中的描述”非阻塞 IO 仅对在 Servlet 中的异步处理请求有效,否则,当调用 ServletInputStream.setReadListener 或ServletOutputStream.setWriteListener 方法时将抛出IllegalStateException“。可以说Servlet3的非阻塞IO是对Servlet3异步的增强。Servlet3的非阻塞是利用java.util.EventListener的事件驱动机制来实现的。

9.1、AsyncLongRunningServlet.java 接收请求,获取读取请求监听器ReadListener

package com.test.servlet3Noblock;

import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * Created by wangxindong on 2017/10/23.
 */
@WebServlet(urlPatterns = "/AsyncLongRunningServlet2", asyncSupported = true)
public class AsyncLongRunningServlet extends HttpServlet {
  protected void doGet(HttpServletRequest request,
             HttpServletResponse response) throws ServletException, IOException {
    request.setCharacterEncoding("UTF-8");
    response.setContentType("text/html;charset=UTF-8");

    AsyncContext actx = request.startAsync();//通过request获得AsyncContent对象

    actx.setTimeout(30*3000);//设置异步调用超时时长

    ServletInputStream in = request.getInputStream();
    //异步读取(实现了非阻塞式读取)
    in.setReadListener(new MyReadListener(in,actx));
    //直接输出到页面的内容(不等异步完成就直接给页面)
    PrintWriter out = response.getWriter();
    out.println("<h1>直接返回页面,不等异步处理结果了</h1>");
    out.flush();
  }

}

9.2、MyReadListener.java 异步处理

package com.test.servlet3Noblock;

import javax.servlet.AsyncContext;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * Created by wangxindong on 2017/10/23.
 */
public class MyReadListener implements ReadListener {
  private ServletInputStream inputStream;
  private AsyncContext asyncContext;
  public MyReadListener(ServletInputStream input,AsyncContext context){
    this.inputStream = input;
    this.asyncContext = context;
  }
  //数据可用时触发执行
  @Override
  public void onDataAvailable() throws IOException {
    System.out.println("数据可用时触发执行");
  }

  //数据读完时触发调用
  @Override
  public void onAllDataRead() throws IOException {
    try {
      Thread.sleep(3000);//暂停5秒,模拟耗时处理数据
      PrintWriter out = asyncContext.getResponse().getWriter();
      out.write("数据读完了");
      out.flush();
      System.out.println("数据读完了");
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

  }

  //数据出错触发调用
  @Override
  public void onError(Throwable t){
    System.out.println("数据 出错");
    t.printStackTrace();
  }
}

十、总结

通讯模型中的NIO可以利用很少的线程处理大量的连接,提高了机器的吞吐量。Servlet的异步处理机制使得我们可以将请求异步到独立的业务线程去执行,使得我们能够将请求线程和业务线程分离。通讯模型的NIO跟Servlet3的异步没有直接关系。但是我们将两种技术同时使用就更增加了以tomcat为容器的系统的处理能力。自从Servlet3.1以后增加了非阻塞的IO,这里的非阻塞IO是面向inputstream和outputstream流,通过jdk的事件驱动模型来实现,更一步增强了Servlet异步的高性能,可以认为是一种增强版的异步机制。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Tomcat怎么实现异步Servlet

    有时Servlet在生成响应报文前必须等待某些耗时的操作,比如在等待一个可用的JDBC连接或等待一个远程Web服务的响应.对于这种情况servlet规范中定义了异步处理方式,由于Servlet中等待阻塞会导致Web容器整体的处理能力低下,所以对于比较耗时的操作可以放置到另外一个线程中进行处理,此过程保留连接的请求和响应对象,在处理完成之后可以把处理的结果通知到客户端. 下面先看Servlet在同步情况下的处理过程,如图所示,Tomcat的客户端请求由管道处理最后会通过Wrapper容器的管道,这

  • jquery请求servlet实现ajax异步请求的示例

    ajax可以发送异步请求实现无刷新效果,但是使用javascript比较麻烦,就query提供了一些封装的方法 ,可以使得操作更为简单: $.ajax()方法: function sendRequest() { $.ajax({ url: "Hello", type: "GET", dataType: "txt", data: "name=zhangsan", complete: function(result){ alert

  • Spring Boot实现异步请求(Servlet 3.0)

    在spring 3.2 及以后版本中增加了对请求的异步处理,旨在提高请求的处理速度降低服务性能消耗. 在我们的请求中做了耗时处理,当并发请求的情况下,为了避免web server的连接池被长期占用而引起性能问题,调用后生成一个非web的服务线程来处理,增加web服务器的吞吐量. 为此 Servlet 3.0 新增了请求的异步处理,Spring 也在此基础上做了封装处理. 本文还是以代码例子的方式说明如何在 Spring Boot 中应用异步请求. 首先说一下几个要点: 1.@WebFilter

  • 详解Servlet 3.0/3.1 中的异步处理

    在Servlet 3.0之前,Servlet采用Thread-Per-Request的方式处理请求,即每一次Http请求都由某一个线程从头到尾负责处理.如果一个请求需要进行IO操作,比如访问数据库.调用第三方服务接口等,那么其所对应的线程将同步地等待IO操作完成, 而IO操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,在并发量越来越大的情况下,这将带来严重的性能问题.即便是像Spring.Struts这样的高层框架也脱离不了这样的桎梏,因为他们都是建立在Servlet之上的.

  • AJAX Servlet实现数据异步交互的方法

    在慕课网上看了AJAX的一些教程,自己参考着实现一下! 首先,导入json所需要的6个包 下载链接:JSONObjectjar_jb51.rar 总的目录: 前端页面: 首先是一个输入框: <input type="text" id="keyword" name="keyword" onkeyup="getContents()"> onkeyup表示按下键盘时的操作 javascript: <script t

  • java基于servlet的文件异步上传

    在这里使用了基于servlet的文件异步上传,好了废话不多说,直接上代码了... package com.future.zfs.util; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.Iterator; import java.util.List; import javax.servlet.ServletException; import javax.s

  • Jquery+ajax+JAVA(servlet)实现下拉菜单异步取值

    首先来看工程结构图: 项目所需要的包,如下所示: JSP代码: <%@ page language="java" import="java.util.*" pageEncoding="GBK"%> <% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServe

  • 浅谈servlet3异步原理与实践

    一.什么是Servlet servlet 是基于 Java 的 Web 组件,由容器进行管理,来生成动态内容.像其他基于 Java 的组件技术一样,servlet 也是基于平台无关的 Java 类格式,被编译为平台无关的字节码,可以被基于 Java 技术的 Web 服务器动态加载并运行.容器(Container),有时候也叫做 servlet 引擎,是 Web 服务器为支持 servlet 功能扩展的部分.客户端通过 servlet 容器实现的 request/response paradigm

  • 浅谈vue异步数据影响页面渲染

    今天遇到一个问题,要保证页面渲染前请求的数据已经得到了 由于user是在异步请求之后保存在session中,而在页面渲染时session中还没有user,页面直接报错. 因此我希望能在所有请求都得到后再去做页面的渲染. 1.先把id为app的div用v-if="appShow",定义appShow为false进行隐藏,避免渲染 2.写计数器,每1ms进行一次查询,如果session中已经有user,删除过滤器,移除滤布,appShow为true,开始渲染页面,这样可以保证页面的正常渲染

  • 浅谈Python爬虫原理与数据抓取

    通用爬虫和聚焦爬虫 根据使用场景,网络爬虫可分为通用爬虫和聚焦爬虫两种. 通用爬虫 通用网络爬虫 是 捜索引擎抓取系统(Baidu.Google.Yahoo等)的重要组成部分.主要目的是将互联网上的网页下载到本地,形成一个互联网内容的镜像备份. 通用搜索引擎(Search Engine)工作原理 通用网络爬虫从互联网中搜集网页,采集信息,这些网页信息用于为搜索引擎建立索引从而提供支持,它决定着整个引擎系统的内容是否丰富,信息是否即时,因此其性能的优劣直接影响着搜索引擎的效果. 第一步:抓取网页

  • 浅谈JavaScript异步编程

    在一年前初学js的时候,看过很多关于异步编程的讲解.但是由于实践经验少,没有办法理解的太多,太理论的东西也往往是看完就忘. 经过公司的三两个项目的锻炼,终于对js异步编程有了比较具体的理解.但始终入门较浅,在这里就当是给自己一个阶段性的总结. 在异步编程中,一条语句的执行不能依赖上一条语句执行完毕的结果,因为无法预测一条语句什么时候执行完毕,它与代码顺序无关,语句是并发执行的. 例如以下代码: $.get($C.apiPath+'ucenter/padCharge/findMember',{id

  • 浅谈Node异步编程的机制

    本文介绍了Node异步编程,分享给大家,具体如下: 目前的异步编程主要解决方案有: 事件发布/订阅模式 Promise/Deferred模式 流程控制库 事件发布/订阅模式 Node自身提供了events模块,可以轻松实现事件的发布/订阅 //订阅 emmiter.on("event1",function(message){ console.log(message); }) //发布 emmiter.emit("event1","I am mesaage!

  • 浅谈Express异步进化史

    1.导言 在 Javascript 的世界里,异步(由于JavaScript的单线程运行,所以JavaScript中的异步是可以阻塞的)无处不在. Express 是 node 环境中非常流行的Web服务端框架,有很大比例的 Node Web应用 采用了 Express. 当使用 JavaScript 编写服务端代码时,我们无可避免的会大量使用到异步.随着 JavaScript.Node 的进化,我们的异步处理方式,也就随之进化. 接下来,我们就来看看 Express 中异步处理的进化过程. 2

  • 浅谈MySQL排序原理与案例分析

    前言 排序是数据库中的一个基本功能,MySQL也不例外.用户通过Order by语句即能达到将指定的结果集排序的目的,其实不仅仅是Order by语句,Group by语句,Distinct语句都会隐含使用排序.本文首先会简单介绍SQL如何利用索引避免排序代价,然后会介绍MySQL实现排序的内部原理,并介绍与排序相关的参数,最后会给出几个"奇怪"排序例子,来谈谈排序一致性问题,并说明产生现象的本质原因. 1.排序优化与索引使用 为了优化SQL语句的排序性能,最好的情况是避免排序,合理利

  • 浅谈jQuery异步对象(XMLHttpRequest)

    我们先来看看异步对象五部曲 这是post请求的. 复制代码 代码如下: //1.00创建异步对象             var xhr = new XMLHttpRequest();             //2.0             xhr.open("post", url,params, true);             //3.0将参数使用Formdata属性传递             xhr.setRequestHeader("Content-Type

  • 浅谈 IPv6 基本技术原理和特点二

    三.几种IPv6应用介绍    从语音.数据到视频,从对现有网络应用更卓越的支持与改善,到IPv6独具特色的创新业务,IPv6带给我们的全方位.高品质的应用与服务前景是美妙而广阔的.以下简单介绍几种IPv6的应用:  1. 视频应用    IPv6对于视频应用的意义在于:解决了地址容量问题,优化了地址结构以提高选路效率,提高了数据吞吐量,以适应视频通信大信息量传输的需要.IPv6还加强了组播功能,实现基于组播.具有网络性能保障的VC视频会议.高清晰度数字电视.VOD视频点播.网络视频监控应用.这

  • 浅谈Spring Boot日志框架实践

    Java应用中,日志一般分为以下5个级别: ERROR 错误信息 WARN 警告信息 INFO 一般信息 DEBUG 调试信息 TRACE 跟踪信息 Spring Boot使用Apache的Commons Logging作为内部的日志框架,其仅仅是一个日志接口,在实际应用中需要为该接口来指定相应的日志实现. SpringBt默认的日志实现是Java Util Logging,是JDK自带的日志包,此外SpringBt当然也支持Log4J.Logback这类很流行的日志实现. 统一将上面这些 日志

随机推荐