文章目录

  • Spring MVC的困境
  • Servlet 异步请求缓解线程池压力
    • Servlet 3.0 异步请求处理
    • Code 演示
      • 工程
      • pom
      • 配置文件
      • 启动类
      • 同步servlet
        • 演示
      • 异步servlet
        • 辅助Code
        • 演示
  • Tomcat 请求处理流程以及异步请求工作原理


Spring MVC的困境

我们先看一段工作中大家常见的代码

@RestController
public class TestAController {@RequestMapping(value ="resource",method = RequestMethod.GET)
public Object processResult(){RestTemplate restTemplateew RestTemplate();∥请求外部资源String result =  restTemplate. getForObject("http://example.com/api/resource2", String.class)return processResultFurther(result);
}private String processResultFurther(String result){return "resource here"
}

Tomcat处理请求,线程状态的变化如下:

我们发现这里的请求和响应事实上 是 同步阻塞

再深入想一下,如果每个线程的执行时间是不可控的,而Tomcat线程池中的线程数量是有限的…

那该怎么办呢?


Servlet 异步请求缓解线程池压力

我们来算一下:

  • TPS : 2000/s
  • 请求耗时:250ms

那么在这种情况下:

  • tomcat最大线程数配置: 2000/s * 0.25s=500

因此 server. tomcat. threads. max=500 基本能满足需求

那假设 tps 到了 4000 呢?

虽然我们可以扩大线程数量,但线程是要消耗操作系统资源的,也并非越多越好,当然了还有其他很多影响因素。

那怎么办呢?


Servlet 3.0 异步请求处理

Filter/Servlet在生成响应之前可能要等待一些资源的响应以完成请求处理,比如一个jdbc查询,或者远程服务rpc调用。

Servlet阻塞等待是一个低效的操作,这将导致受限系统资源急剧紧张,比如线程数、连接数等等

Servlet 3.0引入了异步处理请求的能力,使得线程可以不用阻塞等待,提早返回到容器,从而执行更多的任务请求。把耗时的任务提交给另一个异步线程去执行,以及产生响应


Code 演示

工程


pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.artisan</groupId><artifactId>servlet-asyn</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.7.4</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version></dependency></dependencies></project>

配置文件

server.tomcat.threads.max=1

启动类

package com.artisan;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;/*** @author 小工匠* @version 1.0* @description: 启动类* @date 2022/10/6 12:03* @mark: show me the code , change the world*/@SpringBootApplication
// 使用@ServletComponentScan注解后,Servlet、Filter、Listener可以直接通过@WebServlet、@WebFilter、@WebListener注解自动注册,无需其他代码
@ServletComponentScan
public class ServletAsyncApplication {public static void main(String[] args) {SpringApplication.run(ServletAsyncApplication.class,args);}
}

同步servlet

package com.artisan.servlet;import lombok.extern.slf4j.Slf4j;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.TimeUnit;/*** @author 小工匠* @version 1.0* @description: 同步Servlet请求* @date 2022/10/6 12:06* @mark: show me the code , change the world*/@WebServlet(value = "/sync")
@Slf4j
public class SyncServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {log.info("request: {}, currentThread: {}", req.getQueryString(), Thread.currentThread().getName());processFuture(req, resp);}private void processFuture(HttpServletRequest req, HttpServletResponse resp) {try {TimeUnit.SECONDS.sleep(10);resp.getWriter().println("sync handler");} catch (IOException  | InterruptedException e) {throw new RuntimeException(e);}}}

演示


异步servlet

package com.artisan.servlet;import com.artisan.handler.AsyncRequestWrapper;
import com.artisan.handler.AsyncServletRejectedHandler;
import com.artisan.handler.AsyncThreadFactory;
import lombok.extern.slf4j.Slf4j;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.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;/*** @author 小工匠* @version 1.0* @description: 异步Servlet请求* @date 2022/10/6 15:23* @mark: show me the code , change the world*/@WebServlet(value = "/async", asyncSupported = true)
@Slf4j
public class AsyncServlet extends HttpServlet {private ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.SECONDS,new LinkedBlockingDeque<>(1),AsyncThreadFactory.builder().threadName("async-thread-pool").build(),new AsyncServletRejectedHandler());@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {log.info("request: {}, currentThread: {}", req.getQueryString(), Thread.currentThread().getName());AsyncContext asyncContext = req.startAsync();AsyncRequestWrapper wrapper = AsyncRequestWrapper.builder().asyncContext(asyncContext).servletRequest(req).servletResponse(resp).thread(Thread.currentThread()).build();executor.execute(wrapper);}
}

辅助Code

【AsyncThreadFactory 】

package com.artisan.handler;import lombok.Builder;import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;/*** @author 小工匠* @version 1.0* @description: 线程池工厂* @date 2022/10/6 15:35* @mark: show me the code , change the world*/@Builder
public class AsyncThreadFactory implements ThreadFactory {private final ThreadFactory threadFactory = Executors.defaultThreadFactory();private String threadName;private final AtomicInteger atomicInteger = new AtomicInteger(1);@Overridepublic Thread newThread(Runnable r) {Thread thread = threadFactory.newThread(r);thread.setName(this.threadName + "-" + atomicInteger.getAndIncrement());return thread;}
}

【AsyncRequestWrapper 】

package com.artisan.handler;import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;import javax.servlet.AsyncContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.concurrent.TimeUnit;/*** @author 小工匠* @version 1.0* @description: 线程包装对象* @date 2022/10/6 15:42* @mark: show me the code , change the world*/@Slf4j
@Data
@Builder
public class AsyncRequestWrapper implements Runnable {private AsyncContext asyncContext;private ServletRequest servletRequest;private ServletResponse servletResponse;private Thread thread;@Overridepublic void run() {processFuture(asyncContext, servletRequest, servletResponse);}private void processFuture(AsyncContext asyncContext, ServletRequest servletRequest, ServletResponse servletResponse) {HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest;try {TimeUnit.SECONDS.sleep(10);log.info("processFuture 当前处理线程 {}",  Thread.currentThread().getName());servletResponse.getWriter().println("async handler -->" + httpServletRequest.getQueryString());} catch (IOException | InterruptedException e) {throw new RuntimeException(e);}// 完成asyncContext.complete();}
}

【AsyncServletRejectedHandler】

package com.artisan.handler;import lombok.extern.slf4j.Slf4j;import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;/*** @author 小工匠* @version 1.0* @description: 拒绝策略* @date 2022/10/6 15:24* @mark: show me the code , change the world*/
@Slf4j
public class AsyncServletRejectedHandler  implements RejectedExecutionHandler {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {AsyncRequestWrapper wrapper = (AsyncRequestWrapper) r ;HttpServletRequest httpServletRequest = (HttpServletRequest) wrapper.getServletRequest();try {String queryString = httpServletRequest.getQueryString();String threadName = wrapper.getThread().getName();log.info("当前线程:{} , 当前线程请求参数:{}", threadName, queryString);wrapper.getServletResponse().getWriter().println("too many request , current thread:" + threadName + " , current param:" + queryString );} catch (IOException e) {throw new RuntimeException(e);}// 别忘了 completewrapper.getAsyncContext().complete();}
}

演示

2022-10-06 21:30:09.700  INFO 7860 --- [nio-8080-exec-1] com.artisan.servlet.AsyncServlet         : request: i=1, currentThread: http-nio-8080-exec-1
2022-10-06 21:30:11.277  INFO 7860 --- [nio-8080-exec-1] com.artisan.servlet.AsyncServlet         : request: i=2, currentThread: http-nio-8080-exec-1
2022-10-06 21:30:12.813  INFO 7860 --- [nio-8080-exec-1] com.artisan.servlet.AsyncServlet         : request: i=3, currentThread: http-nio-8080-exec-1
2022-10-06 21:30:12.813  INFO 7860 --- [nio-8080-exec-1] c.a.handler.AsyncServletRejectedHandler  : 当前线程:http-nio-8080-exec-1 , 当前线程请求参数:i=3
2022-10-06 21:30:15.355  INFO 7860 --- [nio-8080-exec-1] com.artisan.servlet.AsyncServlet         : request: i=4, currentThread: http-nio-8080-exec-1
2022-10-06 21:30:15.355  INFO 7860 --- [nio-8080-exec-1] c.a.handler.AsyncServletRejectedHandler  : 当前线程:http-nio-8080-exec-1 , 当前线程请求参数:i=4
2022-10-06 21:30:19.712  INFO 7860 --- [c-thread-pool-1] com.artisan.handler.AsyncRequestWrapper  : processFuture 当前处理线程 async-thread-pool-1
2022-10-06 21:30:29.719  INFO 7860 --- [c-thread-pool-1] com.artisan.handler.AsyncRequestWrapper  : processFuture 当前处理线程 async-thread-pool-1

Tomcat 请求处理流程以及异步请求工作原理

Spring Webflux - 01 MVC的困境相关推荐

  1. 爸爸又给Spring MVC生了个弟弟叫Spring WebFlux

    作者:李新杰 来自:编程新说 情景引入 很早之前,Java就火起来了,是因为它善于开发和处理网络方面的应用. Java有一个爱好,就是喜欢制定规范标准,但自己又不善于去实现. 反倒是一些服务提供商使用 ...

  2. Spring爸爸又给Spring MVC生了个弟弟叫Spring WebFlux

    情景引入 很早之前,Java就火起来了,是因为它善于开发和处理网络方面的应用. Java有一个爱好,就是喜欢制定规范标准,但自己又不善于去实现. 反倒是一些服务提供商使用它的规范标准来制造应用服务器而 ...

  3. Spring WebFlux – SpringReact式编程

    Spring WebFlux is the new module introduced in Spring 5. Spring WebFlux is the first step towards re ...

  4. Spring Webflux 响应式编程 (二) - WebFlux编程实战

    第一章 Reactive Stream 第1节 jdk9的响应式流 就是reactive stream,也就是flow.其实和jdk8的stream没有一点关系.说白了就一个发布-订阅模式,一共只有4 ...

  5. Spring WebFlux

    传统的基于Servlet的Web框架,如Spring MVC,在本质上都是阻塞和多线程的,每个连接都会使用一个线程.在请求处理的时候,会在线程池中拉取一个工作者( worker )线程来对请求进行处理 ...

  6. Kotlin 使用 Spring WebFlux 实现响应式编程

    Kotlin 使用 Spring WebFlux 实现响应式编程 IBM的研究称,整个人类文明所获得的全部数据中,有90%是过去两年内产生的.在此背景下,包括NoSQL,Hadoop, Spark, ...

  7. 响应式编程之Spring Webflux

    文章目录 一 .响应式编程 二 .响应式流 (1)JDK9响应式流: (2)Reactor响应式流库 三.Spring WebFlux 1.整合Webflux 2.事件推送 3.实现背压 四.配置数据 ...

  8. Spring WebFlux开门迎客,却来了一位特殊客人

    话说Spring WebFlux已经出现有一段时间了,但是知道他的人并不是很多.这让他很是闷闷不乐. 还有更惨的是,那些敢于吃螃蟹的人在尝试了他之后,有的竟把代码重新改回到Spring MVC的同步模 ...

  9. Spring Webflux简介

    1.Spring Webflux介绍 The original web framework included in the Spring Framework, Spring Web MVC, was ...

最新文章

  1. [zz]struct epoll_event
  2. 谷歌日语输入法电脑版_【Win安卓】谷歌地球电脑专业版和手机清爽版
  3. [Python语音识别项目笔记] 2矩阵标准化和去标准化
  4. redis学习之——redis.conf配置(基本)文件学习
  5. iframe父页面与子页面之间的传值问题
  6. 自动化测试框架-pytest框架入门篇
  7. 配置和运行 MatchNet CVPR 2015 MatchNet: Unifying Feature and Metric Learning for Patch-Based Matching...
  8. STM8L051低功耗实现
  9. 使用phpQuery轻松采集网页内容
  10. 班迪录屏算法注册机!
  11. linux系统安装文网卫士,360主机卫士 linux版的安装/使用/卸载 方法
  12. IP地址和MAC地址, 路由器, 交换机和集线器
  13. 如何利用单片机的ADC模块(或者独立的ADC芯片)得到接入ADC管脚上的实际电压值?
  14. 1004.选择结构习题:奇偶数判断
  15. -Xms -Xmx等jvm参数的含义
  16. 盒子模型有时候会出现设置背景、边框无法撑大和设置内外间距异常,一般来说此类问题的原因是什么?
  17. 【人工智能大作业】A*和IDA*搜索算法解决十五数码(15-puzzle)问题 (Python实现)(启发式搜索)
  18. (转载)library cache lock和library cache pin到底是什么
  19. 呜啦啦啦~我胡汉三又回来了
  20. 09年房地产全行业零利润或亏损

热门文章

  1. android textview 显示一行,且超出自动截断,显示...
  2. 宝马1系两款新车型官方图发布
  3. 基于java-Android平台实现随心明信片系统演示【附项目源码+简要论文说明】
  4. ipset基本用法和保存配置
  5. Google又被人“盯”上了?九爱到底想干吗?
  6. html的一些基本语法学习与实战
  7. 数据库管理-第七十五期 手把手教你搭19c RAC(20230516)
  8. 3DSlicer入门操作教程
  9. Verilog描述——异步时序电路与同步时序电路浅析
  10. postman测试post请求参数为json类型