异步 Servlet 都不懂,谈何 WebFlux?

服务器
我们日常使用的 SpringMVC,基本上都不是异步 Servlet,而学习 WebFlux,异步 Servlet 是基础,因此松哥还是花点时间来和大家聊一聊什么是异步 Servlet,这有助于大家理解我们为什么需要 WebFlux。

[[403236]]

我们日常使用的 SpringMVC,基本上都不是异步 Servlet,而学习 WebFlux,异步 Servlet 是基础,因此松哥还是花点时间来和大家聊一聊什么是异步 Servlet,这有助于大家理解我们为什么需要 WebFlux。

1.什么是异步 Servlet

先来说说什么是非异步 Servlet。

在 Servlet3.0 之前,Servlet 采用 Thread-Per-Request 的方式处理 Http 请求,即每一次请求都是由某一个线程从头到尾负责处理。

如果一个请求需要进行 IO 操作,比如访问数据库、调用第三方服务接口等,那么其所对应的线程将同步地等待 IO 操作完成, 而 IO 操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,如果并发量很大的话,那肯定会造性能问题。

传统的 MVC 框架如 SpringMVC 也无法摆脱 Servlet 的桎梏,原因很简单,他们都是基于 Servlet 来实现的。

为了解决这一问题,Servlet3.0 中引入了异步 Servlet,然后在 Servlet3.1 中又引入了非阻塞 IO 来进一步增强异步处理的性能。

在正式开整 WebFlux 之前,我们先来了解下异步 Servlet 的一些基本玩法。

2.版本关系

我们要先看看 Servlet 和 Tomcat 之间的对应关系,毕竟异步 Servlet 这种事,用错了 Tomcat 版本可能就不支持了。

下图来自 Tomcat 官网(http://tomcat.apache.org/whichversion.html):

 

从上图我们可以看出,Servlet3.0 对应的 Tomcat 版本是 7.0.x,Servlet3.1 对应的 Tomcat 版本是 8.0.x。

换句话说,如果我们要使用异步 Servlet,Tomcat 至少要 7.0 以上的版本;如果你还想体验一把非阻塞 IO,那么 Tomcat 至少要 8.0 以上。

接下来的案例小伙伴们记得选好自己本地的 Tomcat 版本。

3.基本玩法

先来看一个大家熟悉的同步 Servlet:

  1. @WebServlet(urlPatterns = "/sync"
  2. public class SyncServlet extends HttpServlet { 
  3.     @Override 
  4.     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 
  5.         doGet(request, response); 
  6.     } 
  7.  
  8.     @Override 
  9.     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 
  10.         long start = System.currentTimeMillis(); 
  11.         printLog(request, response); 
  12.         System.out.println("总耗时:" + (System.currentTimeMillis() - start)); 
  13.     } 
  14.  
  15.     private void printLog(HttpServletRequest request, HttpServletResponse response) throws IOException { 
  16.         try { 
  17.             Thread.sleep(3000); 
  18.         } catch (InterruptedException e) { 
  19.             e.printStackTrace(); 
  20.         } 
  21.         response.getWriter().write("ok"); 
  22.     } 

这个 Servlet 大家再熟悉不过了。

前端请求到达后,我们调用 printLog 方法做一些处理,同时把 doGet 方法执行耗时打印出来。

在 printLog 中,我们先休息 3s,然后给前端返回一个字符串给前端。

前端发送请求,最终 doGet 方法中耗时 3001 毫秒。

这是我们大家熟知的同步 Servlet。在整个请求处理过程中,请求会一直占用 Servlet 线程,直到一个请求处理完毕这个线程才会被释放。

接下来我们对其稍微进行改造,使之变为一个异步 Servlet。

有人可能会说,异步有何难?直接把 printLog 方法扔到子线程里边去执行不就行了?但是这样会有另外一个问题,子线程里边没有办法通过 HttpServletResponse 直接返回数据,所以我们一定需要 Servlet 的异步支持,有了异步支持,才可以在子线程中返回数据。

我们来看改造后的代码:

  1. @WebServlet(urlPatterns = "/async",asyncSupported = true
  2. public class AsyncServlet extends HttpServlet { 
  3.     @Override 
  4.     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 
  5.         doGet(request, response); 
  6.     } 
  7.  
  8.     @Override 
  9.     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 
  10.         long start = System.currentTimeMillis(); 
  11.         AsyncContext asyncContext = request.startAsync(); 
  12.         CompletableFuture.runAsync(() -> printLog(asyncContext,asyncContext.getRequest(),asyncContext.getResponse())); 
  13.         System.out.println("总耗时:" + (System.currentTimeMillis() - start)); 
  14.     } 
  15.  
  16.     private void printLog(AsyncContext asyncContext, ServletRequest request, ServletResponse response){ 
  17.         try { 
  18.             Thread.sleep(3000); 
  19.             response.getWriter().write("ok"); 
  20.             asyncContext.complete(); 
  21.         } catch (InterruptedException | IOException e) { 
  22.             e.printStackTrace(); 
  23.         } 
  24.     } 

这里的改造主要有如下几方面:

  1. @WebServlet 注解上添加 asyncSupported 属性,开启异步支持。
  2. 调用 request.startAsync(); 方法开启异步上下文。
  3. 通过 JDK8 中的 CompletableFuture.runAsync 方法来启动一个子线程(当然也可以自己 new 一个子线程)。
  4. 调用 printLog 方法时的 request 和 response 重新构造,直接从 asyncContext 中获取,注意,这点是【关键】。
  5. 在 printLog 方法中,方法执行完成后,调用 asyncContext.complete() 方法通知异步上下文请求处理完毕。

经过上面的改造之后,现在的控制台打印出来的总耗时几乎可以忽略不计了。

也就是说,有了异步 Servlet 之后,后台 Servlet 的线程会被及时释放,释放之后又可以去接收新的请求,进而提高应用的并发能力。

第一次接触异步 Servlet 的小伙伴可能会有一个误解,以为用了异步 Servlet 后,前端的响应就会加快。这个怎么说呢?后台的并发能力提高了,前端的响应速度自然会提高,但是我们一两个简单的请求是很难看出这种提高的。

4.小结

好啦,今天就和大家分享一下异步 Servlet,作为 WebFlux 的一个前奏。至此,我们的 WebFlux 前奏已经更新了五篇了,即将进入 WebFlux 的殿堂。

本文转载自微信公众号「江南一点雨」,可以通过以下二维码关注。转载本文请联系江南一点雨公众号。

 

责任编辑:武晓燕 来源: 江南一点雨
相关推荐

2024-03-06 07:52:21

Spring框架响应式编程微服务架构

2013-09-02 09:18:59

2022-06-07 09:30:02

Linux内存

2021-11-02 09:55:57

Linux内核内存

2020-12-08 09:13:51

MySQLDDL变更

2020-08-18 10:19:57

华为云

2022-03-15 08:51:27

量子计算机量子加密普通加密

2018-12-07 11:12:16

Linux运维内核

2020-06-22 08:16:16

哈希hashCodeequals

2022-05-14 08:05:18

Linux内存管理

2019-07-18 15:42:53

Redisoffer数据库

2010-07-01 16:33:08

UDP协议

2009-07-07 09:41:02

异步ServletAJAX

2018-03-28 21:40:03

2021-05-10 16:42:52

数据AI计算机

2020-09-08 06:32:57

项目低耦合高内聚

2022-06-10 09:30:59

IBM

2012-05-24 13:24:00

Linux操作系统

2010-01-14 09:15:07

Java EE 6Servlet 3.0异步处理

2021-09-29 09:18:24

Linux 内核运维
点赞
收藏

51CTO技术栈公众号