Spring Webflux - 01 MVC的困境
文章目录
- 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的困境相关推荐
- 爸爸又给Spring MVC生了个弟弟叫Spring WebFlux
作者:李新杰 来自:编程新说 情景引入 很早之前,Java就火起来了,是因为它善于开发和处理网络方面的应用. Java有一个爱好,就是喜欢制定规范标准,但自己又不善于去实现. 反倒是一些服务提供商使用 ...
- Spring爸爸又给Spring MVC生了个弟弟叫Spring WebFlux
情景引入 很早之前,Java就火起来了,是因为它善于开发和处理网络方面的应用. Java有一个爱好,就是喜欢制定规范标准,但自己又不善于去实现. 反倒是一些服务提供商使用它的规范标准来制造应用服务器而 ...
- Spring WebFlux – SpringReact式编程
Spring WebFlux is the new module introduced in Spring 5. Spring WebFlux is the first step towards re ...
- Spring Webflux 响应式编程 (二) - WebFlux编程实战
第一章 Reactive Stream 第1节 jdk9的响应式流 就是reactive stream,也就是flow.其实和jdk8的stream没有一点关系.说白了就一个发布-订阅模式,一共只有4 ...
- Spring WebFlux
传统的基于Servlet的Web框架,如Spring MVC,在本质上都是阻塞和多线程的,每个连接都会使用一个线程.在请求处理的时候,会在线程池中拉取一个工作者( worker )线程来对请求进行处理 ...
- Kotlin 使用 Spring WebFlux 实现响应式编程
Kotlin 使用 Spring WebFlux 实现响应式编程 IBM的研究称,整个人类文明所获得的全部数据中,有90%是过去两年内产生的.在此背景下,包括NoSQL,Hadoop, Spark, ...
- 响应式编程之Spring Webflux
文章目录 一 .响应式编程 二 .响应式流 (1)JDK9响应式流: (2)Reactor响应式流库 三.Spring WebFlux 1.整合Webflux 2.事件推送 3.实现背压 四.配置数据 ...
- Spring WebFlux开门迎客,却来了一位特殊客人
话说Spring WebFlux已经出现有一段时间了,但是知道他的人并不是很多.这让他很是闷闷不乐. 还有更惨的是,那些敢于吃螃蟹的人在尝试了他之后,有的竟把代码重新改回到Spring MVC的同步模 ...
- Spring Webflux简介
1.Spring Webflux介绍 The original web framework included in the Spring Framework, Spring Web MVC, was ...
最新文章
- [zz]struct epoll_event
- 谷歌日语输入法电脑版_【Win安卓】谷歌地球电脑专业版和手机清爽版
- [Python语音识别项目笔记] 2矩阵标准化和去标准化
- redis学习之——redis.conf配置(基本)文件学习
- iframe父页面与子页面之间的传值问题
- 自动化测试框架-pytest框架入门篇
- 配置和运行 MatchNet CVPR 2015 MatchNet: Unifying Feature and Metric Learning for Patch-Based Matching...
- STM8L051低功耗实现
- 使用phpQuery轻松采集网页内容
- 班迪录屏算法注册机!
- linux系统安装文网卫士,360主机卫士 linux版的安装/使用/卸载 方法
- IP地址和MAC地址, 路由器, 交换机和集线器
- 如何利用单片机的ADC模块(或者独立的ADC芯片)得到接入ADC管脚上的实际电压值?
- 1004.选择结构习题:奇偶数判断
- -Xms -Xmx等jvm参数的含义
- 盒子模型有时候会出现设置背景、边框无法撑大和设置内外间距异常,一般来说此类问题的原因是什么?
- 【人工智能大作业】A*和IDA*搜索算法解决十五数码(15-puzzle)问题 (Python实现)(启发式搜索)
- (转载)library cache lock和library cache pin到底是什么
- 呜啦啦啦~我胡汉三又回来了
- 09年房地产全行业零利润或亏损