2

异步 Servlet 都不懂,谈何 WebFlux?

 2 years ago
source link: http://www.javaboy.org/2021/0602/webflux-async-servlet.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
19 天前 10 分钟 读完 (大约 1506 个字)

异步 Servlet 都不懂,谈何 WebFlux?

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

1.什么是异步 Servlet

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

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

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

传统的 MVC 框架如 SpringMVC 也无法摆脱 Servlet 的桎梏,原因很简单,他们都是基于 Servlet 来实现的。如 SpringMVC 中大家所熟知的 DispatcherServlet(如果大家对于 SpringMVC 的原理不太理解,可以查看松哥之前的系列文章SpringMVC源码解读系列,20 篇干货完美收官!)。

为了解决这一问题,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:

@WebServlet(urlPatterns = "/sync")
public class SyncServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
long start = System.currentTimeMillis();
printLog(request, response);
System.out.println("总耗时:" + (System.currentTimeMillis() - start));
}

private void printLog(HttpServletRequest request, HttpServletResponse response) throws IOException {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
response.getWriter().write("ok");
}
}

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

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

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

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

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

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

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

我们来看改造后的代码:

@WebServlet(urlPatterns = "/async",asyncSupported = true)
public class AsyncServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
long start = System.currentTimeMillis();
AsyncContext asyncContext = request.startAsync();
CompletableFuture.runAsync(() -> printLog(asyncContext,asyncContext.getRequest(),asyncContext.getResponse()));
System.out.println("总耗时:" + (System.currentTimeMillis() - start));
}

private void printLog(AsyncContext asyncContext, ServletRequest request, ServletResponse response){
try {
Thread.sleep(3000);
response.getWriter().write("ok");
asyncContext.complete();
} catch (InterruptedException | IOException e) {
e.printStackTrace();
}
}
}

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

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

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

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

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

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

前四篇前置知识传送门:

喜欢这篇文章吗?扫码关注公众号【江南一点雨】【江南一点雨】专注于 SPRING BOOT+微服务以及前后端分离技术,每天推送原创技术干货,关注后回复 JAVA,领取松哥为你精心准备的 JAVA 干货!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK