Spring Webflux - 01 MVC的困境

article/2025/8/29 2:23:23

文章目录

  • 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 请求处理流程以及异步请求工作原理

在这里插入图片描述

在这里插入图片描述


http://chatgpt.dhexx.cn/article/8r656IuI.shtml

相关文章

WebFlux的使用

什么是WebFlux springWebFlux 是 SpringFrameworlk5.0 添加的新功能&#xff0c;它是完全非阻塞的&#xff0c;支持Reactive Stream及背压&#xff0c;可以运行于Netty、Undertow等服务器&#xff0c;及Servlet 3.1容器。 webflux主要在如下两方面体现出独有的优势&#xff1a;…

webFlux入门

今天发现一个特别好的文章&#xff0c;是关于springBoot框架中响应式编程的&#xff0c;那下面就是这位博主所整理的一些干货&#x1f447; ------------------------------------------------------------ 1. WebFlux介绍 Spring WebFlux 是 Spring Framework 5.0中引入的新…

WebFlux 简介

目录 一、关于WebFlux 二、SpringMVC与SpringWebFlux 三、Reactive Spring Web HttpHandler WebHandler 四、实现WebFlux 实例 基于Annotated Controller方式实现 WebFluxConfig配置&#xff1a; Controller: Main方法&#xff1a; 函数式编程方式 集成Thymeleaf sp…

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

第一章 Reactive Stream 第1节 jdk9的响应式流 就是reactive stream&#xff0c;也就是flow。其实和jdk8的stream没有一点关系。说白了就一个发布-订阅模式&#xff0c;一共只有4个接口&#xff0c;3个对象&#xff0c;非常简单清晰。 什么是背压&#xff1f; 背压是指订阅者…

WebFlux 详解

今天我们开始来学习下 WebFlux&#xff0c;为什么突然要学这个东西&#xff1f; 因为我之前是想学习 Spring Cloud Gateway 来着&#xff0c;然后发现它是基于 Spring5.0SpringBoot2.0WebFlux等技术开发的。所以学之前才要来简单了解下 WebFlux 技术。 然后要学习 WebFlux 时…

Redis常用数据类型及其对应的底层数据结构

Redis数据库 Redis是一种键值(Key-Value)数据库。相较于MySQL之类的关系型数据库&#xff0c;Redis是一种非关系型数据库。Redis存储的数据只包含键和值两部分&#xff0c;只能通过键来查询值。这样简单的存储结构&#xff0c;能让Redis的读写效率非常高(HashMap读写效率都是O…

二. Redis 数据类型

2.1 Redis 字符串 (String) 2.1.1 概述 String 是 Redis 最基本的类型&#xff0c;你可以理解成与 Memcached 一模一样的类型&#xff0c;一个 key 对应一个 value。 String 类型是二进制安全的。意味着 Redis 的 string 可以包含任何数据。比如 jpg 图片或者序列化的对象。 …

Redis数据类型Hash

文章目录 Hash类型介绍hash 类型数据的基本操作 hash 类型数据操作的注意事项Hash和String类型的区别 有时候我们往往不是在缓存中存一个值&#xff0c;而是选择存一个对象&#xff0c;比如一个购物车消息&#xff0c;我们就需要使用到hash了 Hash类型介绍 新的存储需求&…

Redis数据类型String

文章目录 数据存储类型介绍String类型String类型的基本操作String单数据操作和多数据操作的选择问题string 类型数据的扩展操作数据增加指定范围的值String 设置数据指定的生命周期string 类型数据操作的注意事项 数据存储类型介绍 常用的五个数据类型: string --------------…

Redis数据类型 - 散列(Map)

文章目录 一、散列简介二、散列的基本操作三、散列与字符串比较1、散列键的优点2、字符串键的优点 一、散列简介 散列就是hash或者说Map&#xff0c;Redis的散列键会将一个键和一个散列在数据库中关联起来&#xff0c;可以在散列中设置任意多个字符串键值对&#xff0c;因此通…

redis数据类型插入输出命令

进入客户机&#xff1a;redis-cli 中文字符不能显示&#xff1a;redis-cli --raw、get Course:1:Cname 一、redis数据类型数据的添加 1、String 添加数据&#xff1a;set StringTest(数据名称) “helloword”(数据) 显示数据&#xff1a;get StringTest&#xff08;数据名称&am…

Redis数据类型及使用场景

转自&#xff1a; http://www.kubiji.cn/juhe-id7106.html Redis数据类型及使用场景 来源&#xff1a; WQTech阅读&#xff1a; 2936 时间&#xff1a;2 小时前 摘要&#xff1a;Redis相比其它的KV数据库,其一大特点是支持丰富的数据类型.它一共支持5种数据类型,下面逐一介绍这…

Redis数据类型与操作命令

1. 键值对数据库 1.1 redis数据结构 redis的数据是 key-value 形式的键值对&#xff0c;其中 key 其实都是字符串的形式&#xff0c;而 value 的数据类型&#xff0c;也就是数据的保存形式&#xff0c;底层实现的方式就用到了数据结构。 所以我们一直说的“redis五种数据结构…

Redis数据类型

文章目录 STRINGLISTSETHASHZSET Redis主要有5种数据类型&#xff0c;包括String&#xff0c;List&#xff0c;Set&#xff0c;Zset&#xff0c;Hash&#xff0c;满足大部分的使用要求&#xff0c;Redis各数据类型的使用场景可以参考Redis使用场景 数据类型可以存储的值操作ST…

redis数据类型(5种)和底层实现

redis数据类型(5种)和底层实现 Redis的特点 要用好Redis&#xff0c;首先要明白它的特点&#xff1a; 读写速度快。redis官网测试读写能到10万左右每秒。速度快的原因这里简单说一下&#xff0c;第一是因为数据存储在内存中&#xff0c;我们知道机器访问内存的速度是远远大于…

Redis数据类型及编码

Redis数据类型及编码 说到Redis的数据类型&#xff0c;我们大概会很快想到Redis的5种常见的数据类型&#xff1a;字符串(String)、列表(List)、散列(Hash)、集合(Set)、有序集合(Sorted Set)&#xff0c;以及他们的特点和运用场景及常用命令。不过在讲五大数据类型之前&#x…

Redis 基础 -- Redis数据类型之set

文章目录 1. Redis数据类型之set1.1 set类型介绍1.2 set类型基本操作1.3 set 类型数据的扩展操作&#xff1a;获取随机的数据1.4 set 类型数据的扩展操作&#xff1a;集合的交、并、差集1.4.1 sinter命令1.4.2 sunion命令1.4.3 sdiff命令1.4.4 sinterstore命令1.4.5 sunionstor…

Redis 数据类型

1、string类型 &#xff08;1&#xff09;存储的数据&#xff1a;单个数据&#xff0c;最简单的数据存储类型&#xff0c;也是最常用的数据存储类型。 string&#xff0c;他就是存一个字符串儿&#xff0c;注意是value那一部分是一个字符串&#xff0c;它是redis中最基本、最…

NoSQL数据库之Redis(三):常用五大数据类型

目录 Redis键(key)常用命令 Redis字符串(String)常用命令原子性数据结构 Redis列表(List)常用命令数据结构 Redis集合(Set)常用命令数据结构 Redis哈希(Hash)常用命令数据结构 Redis有序集合Zset(sorted set)常用命令数据结构跳跃表&#xff08;跳表&#xff09; redis常见数据…

redis的五种数据类型

🏆作者简介:哪吒,CSDN2022博客之星Top1、CSDN2021博客之星Top2、多届新星计划导师✌、博客专家💪 ,专注Java硬核干货分享,立志做到Java赛道全网Top N。 🏆本文收录于,Java基础教程系列,目前已经700+订阅,CSDN最强Java专栏,包含全部Java基础知识点、Java8新特性、…