资讯专栏INFORMATION COLUMN

Servlet 3.1 Async IO分析

n7then / 1007人阅读

摘要:于是提供了机制,使得从中读往里写变成异步动作。这是因为客户端的数据推送速度太慢了,容器先将收回,当容器发现可以读取到新数据的时候,再分配一个去读,如此循环直到全部读完为止。注意和不能同时使用。

Github地址

相关系列文章:

Servlet 3.0 异步处理详解

Spring MVC异步处理的几种方式

Servlet Async Processing提供了一种异步请求处理的手段(见我的另一篇文章Servlet 3.0 异步处理详解),能够让你将Http thread从慢速处理中释放出来出来其他请求,提高系统的响应度。

但是光有Async Processing是不够的,因为整个请求-响应过程的速度快慢还牵涉到了客户端的网络情况,如果客户端网络情况糟糕,其上传和下载速度都很慢,那么同样也会长时间占用Http Thread使其不能被释放出来。

于是Servlet 3.1提供了Async IO机制,使得从Request中读、往Response里写变成异步动作。

Async Read

我们先来一段客户端上传速度慢的例子,AsyncReadServlet.java:

@WebServlet(value = "/async-read", asyncSupported = true)
public class AsyncReadServlet extends HttpServlet {

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    System.out.println("Servlet thread: " + Thread.currentThread().getName());
    AsyncContext asyncCtx = req.startAsync();
    ServletInputStream is = req.getInputStream();
    is.setReadListener(new ReadListener() {
      private int totalReadBytes = 0;

      @Override
      public void onDataAvailable() {
        System.out.println("ReadListener thread: " + Thread.currentThread().getName());

        try {
          byte buffer[] = new byte[1 * 1024];
          int readBytes = 0;
          while (is.isReady() && !is.isFinished()) {
            int length = is.read(buffer);
            if (length == -1 && is.isFinished()) {
              asyncCtx.complete();
              System.out.println("Read: " + readBytes + " bytes");
              System.out.println("Total Read: " + totalReadBytes + " bytes");
              return;
            }
            readBytes += length;
            totalReadBytes += length;

          }
          System.out.println("Read: " + readBytes + " bytes");

        } catch (IOException ex) {
          ex.printStackTrace();
          asyncCtx.complete();
        }
      }

      @Override
      public void onAllDataRead() {
        try {
          System.out.println("Total Read: " + totalReadBytes + " bytes");
          asyncCtx.getResponse().getWriter().println("Finished");
        } catch (IOException ex) {
          ex.printStackTrace();
        }
        asyncCtx.complete();
      }

      @Override
      public void onError(Throwable t) {
        System.out.println(ExceptionUtils.getStackTrace(t));
        asyncCtx.complete();
      }
    });

  }

}

我们利用curl--limit-rate选项来模拟慢速上传curl -X POST -F "bigfile=@src/main/resources/bigfile" --limit-rate 5k http://localhost:8080/async-read

然后观察服务端的打印输出:

Servlet thread: http-nio-8080-exec-3
ReadListener thread: http-nio-8080-exec-3
Read: 16538 bytes
ReadListener thread: http-nio-8080-exec-4
Read: 16384 bytes
ReadListener thread: http-nio-8080-exec-5
Read: 16384 bytes
ReadListener thread: http-nio-8080-exec-7
Read: 16384 bytes
ReadListener thread: http-nio-8080-exec-6
Read: 16384 bytes
ReadListener thread: http-nio-8080-exec-8
Read: 16384 bytes
ReadListener thread: http-nio-8080-exec-9
Read: 16384 bytes
ReadListener thread: http-nio-8080-exec-10
Read: 2312 bytes
ReadListener thread: http-nio-8080-exec-1
Read: 48 bytes
Total Read: 117202 bytes

可以从输出看到除了doGet和第一次进入onDataAvailable是同一个Http thread之外,后面的read动作都发生在另外的Http thread里。
这是因为客户端的数据推送速度太慢了,容器先将Http thread收回,当容器发现可以读取到新数据的时候,再分配一个Http thread去读InputStream,如此循环直到全部读完为止。

注意:HttpServletRequest.getInputStream()getParameter*()不能同时使用。

Async Write

再来一段客户端下载慢的例子,AsyncWriteServlet.java:

@WebServlet(value = "/async-write", asyncSupported = true)
public class AsyncWriteServlet extends HttpServlet {

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    System.out.println("Servlet thread: " + Thread.currentThread().getName());
    AsyncContext asyncCtx = req.startAsync();
    ServletOutputStream os = resp.getOutputStream();
    InputStream bigfileInputStream = ClassLoader.getSystemClassLoader().getResourceAsStream("bigfile");

    os.setWriteListener(new WriteListener() {

      @Override
      public void onWritePossible() throws IOException {

        int loopCount = 0;
        System.out.println("WriteListener thread: " + Thread.currentThread().getName());
        while (os.isReady()) {
          loopCount++;
          System.out.println("Loop Count: " + loopCount);
          byte[] bytes = readContent();
          if (bytes != null) {
            os.write(bytes);
          } else {
            closeInputStream();
            asyncCtx.complete();
            break;
          }
        }
      }

      @Override
      public void onError(Throwable t) {

        try {
          os.print("Error happened");
          os.print(ExceptionUtils.getStackTrace(t));
        } catch (IOException e) {
          e.printStackTrace();
        } finally {
          closeInputStream();
          asyncCtx.complete();
        }

      }

      private byte[] readContent() throws IOException {
        byte[] bytes = new byte[1024];
        int readLength = IOUtils.read(bigfileInputStream, bytes);
        if (readLength <= 0) {
          return null;
        }
        return bytes;
      }

      private void closeInputStream() {
        IOUtils.closeQuietly(bigfileInputStream);
      }
    });

  }

}

同样利用curl做慢速下载,curl --limit-rate 5k http://localhost:8080/async-write

接下来看以下服务端打印输出:

Servlet thread: http-nio-8080-exec-1
WriteListener thread: http-nio-8080-exec-1
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-2
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-3
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-4
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-5
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-6
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-7
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-8
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-9
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-10
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-1
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-2
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-3
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-4
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-5
Write bytes: 2312

PS. 后发现即使没有添加--limit-rate参数,也会出现类似于上面的结果。

Jmeter

上面两个例子使用的是curl来模拟,我们也提供了Jmeter的benchmark。

需要注意的是,必须在user.properties文件所在目录启动Jmeter,因为这个文件里提供了模拟慢速连接的参数httpclient.socket.http.cps=5120。然后利用Jmeter打开benchmark.xml。

相关资料

Java EE 7 Tutorial: Java Servlet Technology - Nonblocking I/O

Slides - Servlet 3.1 Async IO

Non-blocking I/O using Servlet 3.1: Scalable applications using Java EE 7

How to simulate network bandwidth in JMeter?

Configuring JMeter

Servlet 3.1 Asynchronous IO and Jetty-9.1

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/70853.html

相关文章

  • Spring MVC异步处理简介

    摘要:异步处理简介地址相关系列文章异步处理详解分析本文讲到的所有特性皆是基于的,不是基于的。用于异步返回结果,使用自己的,使用负责处理它。配置执行异步操作需要用到,这个可以在用方法来提供相关文档。 Spring MVC异步处理简介 Github地址 相关系列文章: Servlet 3.0 异步处理详解 Servlet 3.1 Async IO分析 本文讲到的所有特性皆是基于Servlet...

    Sike 评论0 收藏0
  • Java Reactive Web设计与实现

    摘要:概念响应式编程,异步非阻塞就是响应式编程,与之相对应的是命令式编程。的另外一种实现方式就是消息队列。非阻塞设计利用规范中的实现实现代码链接 注: 本文是由读者观看小马哥公开课视频过程中的笔记整理而成。更多Spring Framework文章可参看笔者个人github: spring-framework-lesson 。 0. 编程模型与并发模型 Spring 5实现了一部分Reacti...

    siberiawolf 评论0 收藏0
  • Servlet 3.0 异步处理详解

    摘要:但是这样依然有一个问题,考虑以下场景有一个容器,线程池大小。这个时候工程师发现了问题,扩展了线程池大小到,但是负载依然持续走高,现在有个到,依然无法响应。你可以修改的线程池大小,把它和比较结果来验证这一结论。 Github地址 相关系列文章: Servlet 3.1 Async IO分析 Spring MVC异步处理的几种方式 Servlet 3.0 开始提供了AsyncConte...

    kid143 评论0 收藏0
  • AJAX入门这一篇就够了

    摘要:性能会有所降低一点内容,刷新整个页面用户的操作页面会中断整个页面被刷新了就是能够做到局部刷新三对象是中最重要的一个对象。头信息已经接收,响应数据尚未接收。 一、什么是Ajax Ajax(Asynchronous JavaScript and XML) 异步JavaScript和XML Ajax实际上是下面这几种技术的融合: (1)XHTML和CSS的基于标准的表示技术 (2)DOM进...

    tunny 评论0 收藏0
  • Servlet 常见的注解

    摘要:拦截所有的请求,放行登录页面登录操作请求,其余请求需要在登录后才可访问。同时配置参数,指定要放行的路径和请求的字符集。 Serlvet中WebServlet注解 作用: 用于将一个类声明为 Servlet 描述: 该注解将会在部署时被容器处理, 容器将根据具体的属性配置将相应的类部署为 Servlet. 属性详解: 该注解具有下表给出的一些常用属性(以下所有属性均为可选属性, 但是 v...

    hyuan 评论0 收藏0

发表评论

0条评论

n7then

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<